Coursera-Applied Data Science with Python-Introduction to Data Science in Python-Week3
一、Merging Dataframes:
在回顾上周所学内容之前,上一周我们重点学习了Pandas的核心数据存储方式。它分别用于一维和二维数据处理。主要分为两种方法来获取数据值:一种是基于行的操作(使用loc和iloc),另一种则是直接指定列名(借助方括号)。此外,我们还掌握了利用Booleans掩码进行条件筛选的技术。

1.向已存在的DataFrame内添加列:

通过学习相关知识后,我们掌握了在DataFrame中添加新数据的方法:即为带有新列名的方括号操作符赋值以实现这一目标。

然而该方法的一个不足之处在于要求所赋值列表与其对应的DataFrame索引具有相同的长度。换句话说,则需明确指定DataFrame中每一行对应该属性的具体数值。如果无法满足这一条件,则可能导致运行时错误:

需要注意的是,在数量化数据(scalar value)的情况下——包括整数类型和字符串等其他类型的变量——这些方法同样适用于
需要注意的是,在数量化数据(scalar value)的情况下——包括整数类型和字符串等其他类型的变量——这些方法同样适用于

如果我们要为某一列的不同位置赋予不同的值,则需要预先准备一个与该DataFrame索引长度一致的列表,在列表中依次排列所期望的元素(其中包含缺失值)。

当每个数据框中的每一行都具有独特的索引时,则我们可以利用其索引来定位并为目标列的具体位置填充数值。这带来的便利之处就在于无需自行补充缺失的数据。

2.合并DataFrame:
借助Pandas库中的merge函数能够负责整合两个数据框架的操作;此方法遵循着名称规范。
The Pandas.merge function executes an inner join between two DataFrames or Series based on specified keys. This operation can be configured with various options including the join type, keys for alignment, and suffixes for non-overlapping column names. Additional parameters allow for controlling the output's duplication of input data and specifying whether to sort the merged result.
本节将介绍主要参数及其作用。左侧数据源left:用于指定参与合并的第一个DataFrame;右侧数据源right:用于指定参与合并的第二个DataFrame。how用于指定合并方式('inner'、'outer'等)。这与SQL中的join类型相对应。内连接操作返回两个表数据的交集部分;外连接则会整合两个表的所有记录,并包含所有非null字段值;左连接操作会以左边的数据为准查找右边的数据结果;右连接则相反会以右边的数据为准查找左边的数据结果。需要注意的是,在未指明left_on或right_on的情况下,默认会使用双方df.columns来进行匹配运算;如果想修改默认匹配列,则需手动指定left_on和right_on这两个字段名即可完成操作;另外一种常见使用场景则是通过left_index和right_index来分别使用双方df.index来进行匹配运算,默认情况下双方index都会被自动启用并进行配对计算;sort参数则用于规定最终拼接结果中各条记录之间的排序逻辑
suffixes:字符串的元组,用于追加于重叠列名的末尾,默认为(_x,_y)
具体地我们通过代码来理解,先创建两个DateFrame
staff_df = pd.DataFrame([{'Name': 'Kelly', 'Role': 'Director of HR'},
{'Name': 'Sally', 'Role': 'Course liasion'},
{'Name': 'James', 'Role': 'Grader'}])
staff_df = staff_df.set_index('Name')
student_df = pd.DataFrame([{'Name': 'James', 'School': 'Business'},
{'Name': 'Mike', 'School': 'Law'},
{'Name': 'Sally', 'School': 'Engineering'}])
student_df = student_df.set_index('Name')
a.连接情况一:外连接
pd.merge(staff_df, student_df, how='outer', left_index=True, right_index=True)
这段代码的作用在于对staff_df和student_df执行外连接操作,并以各自索引字段作为连接基准进行关联操作。最终所得的数据结果为:

b.连接情况二:内连接
pd.merge(staff_df, student_df, how='inner', left_index=True, right_index=True)

c.连接情况三:左连接
pd.merge(staff_df, student_df, how='left', left_index=True, right_index=True)

d.连接情况四:右连接
pd.merge(staff_df, student_df, how='right', left_index=True, right_index=True)

e.连接情况五:不以索引进行连接,而是以某一列进行连接
staff_df = staff_df.reset_index()
student_df = student_df.reset_index()
pd.merge(staff_df, student_df, how='left', left_on='Name', right_on='Name')
这段代码首先指定staff_df和student_df的索引列为从0开始递增的形式,并将左边DataFrame的连接键设定为Name列的同时(and at the same time),也将右边DataFrame的连接键设定为此列

f.连接情况六:合并的两个DataFrame有两个同名的列:
staff_df = pd.DataFrame([{'Name': 'Kelly', 'Role': 'Director of HR', 'Location': 'State Street'},
{'Name': 'Sally', 'Role': 'Course liasion', 'Location': 'Washington Avenue'},
{'Name': 'James', 'Role': 'Grader', 'Location': 'Washington Avenue'}])
student_df = pd.DataFrame([{'Name': 'James', 'School': 'Business', 'Location': '1024 Billiard Avenue'},
{'Name': 'Mike', 'School': 'Law', 'Location': 'Fraternity House #22'},
{'Name': 'Sally', 'School': 'Engineering', 'Location': '512 Wilson Crescent'}])
pd.merge(staff_df, student_df, how='left', left_on='Name', right_on='Name')
可以看到,在staff_df和student_df中存在两个具有相同名称的字段'Location'(注:此处应为'Columns'),但它们所代表的意义各不相同。在这种情况下(注:此处应为'在这种情况下'),Pandas通常会将这两个字段都保留下

g.连接情况七:多列作为连接键进行合并
staff_df = pd.DataFrame([{'First Name': 'Kelly', 'Last Name': 'Desjardins', 'Role': 'Director of HR'},
{'First Name': 'Sally', 'Last Name': 'Brooks', 'Role': 'Course liasion'},
{'First Name': 'James', 'Last Name': 'Wilde', 'Role': 'Grader'}])
student_df = pd.DataFrame([{'First Name': 'James', 'Last Name': 'Hammond', 'School': 'Business'},
{'First Name': 'Mike', 'Last Name': 'Smith', 'School': 'Law'},
{'First Name': 'Sally', 'Last Name': 'Brooks', 'School': 'Engineering'}])
staff_df
student_df
pd.merge(staff_df, student_df, how='inner', left_on=['First Name','Last Name'], right_on=['First Name','Last Name'])
其中left_on和right_on的参数值是一个list,这就是作为连接键的列名:

二、Idiomatic Pandas:Making Code Pandorable
在Python以及Pandas中解决一个问题的方法并不只有一种,在众多解决方案中有一些被广泛推荐的常用方法(common approaches)。这些常用方法通常具有良好的性能和高可读性特性,在Pandas社区中将这些常用术语称为pandorbable(panda-orean)。为了使代码更加易于可读(pandorbable),有几个关键点需要关注:
1.尽量少用链式索引,多用方法链接:
链式访问方法等同于(df.loc["Washtennaw"]["Total Population"])这样的语句。它的一个缺点是无法预判返回的是副本还是视图,这一结果完全取决于NumPy库的行为。Tom Osberger指出,在连续使用方括号进行数据访问时,请务必谨慎评估其潜在的影响。

然而,在某些情况下
import pandas as pd
df = pd.read_csv('census.csv')
(df.where(df['SUMLEV']==50)
.dropna()
.set_index(['STNAME','CTYNAME'])
.rename(columns={'ESTIMATESBASE2010': 'Estimates Base 2010'}))
df = df[df['SUMLEV']==50]
df.set_index(['STNAME','CTYNAME'], inplace=True)
df.rename(columns={'ESTIMATESBASE2010': 'Estimates Base 2010'})

通过结果来看的话,在本质上这两段代码是相同的。然而个人认为第二段代码对于新手来说更为直观易懂。我们主张认为第一段代码更具可读性
- 采用apply工具和lambda表达式来处理DataFrame的数据。我们基于之前的人口评估数据进行计算:
例1:我们想要找出每行数据中的最大最小值:
import numpy as np
def min_max(row):
data = row[['POPESTIMATE2010',
'POPESTIMATE2011',
'POPESTIMATE2012',
'POPESTIMATE2013',
'POPESTIMATE2014',
'POPESTIMATE2015']]
return pd.Series({'min': np.min(data), 'max': np.max(data)})
df.apply(min_max, axis=1)

apply函数的第一个参数axis=1对应于横向数据处理的方向;第二个参数则为要应用的具体函数min_max;df中的每一行数据都会被施加该函数
import numpy as np
def min_max(row):
data = row[['POPESTIMATE2010',
'POPESTIMATE2011',
'POPESTIMATE2012',
'POPESTIMATE2013',
'POPESTIMATE2014',
'POPESTIMATE2015']]
row['max'] = np.max(data)
row['min'] = np.min(data)
return row
df.apply(min_max, axis=1)

通常情况下,人们不会将大功能模块应用于dataframe的apply方法上,而是普遍采用lambda表达式:
rows = ['POPESTIMATE2010',
'POPESTIMATE2011',
'POPESTIMATE2012',
'POPESTIMATE2013',
'POPESTIMATE2014',
'POPESTIMATE2015']
df.apply(lambda x: np.max(x[rows]), axis=1)

三、Group by
尽管Pandas使得我们能够通过迭代的方式来获取DataFrame中的数据,但该方法运行效率不高且不具备可扩展性.举例而言:
%%timeit -n 10
for state in df['STNAME'].unique():
avg = np.average(df.where(df['STNAME']==state).dropna()['CENSUS2010POP'])
print('Counties in state ' + state + ' have an average population of ' + str(avg))
另外一种实现这一功能的方式是通过调用groupby()函数。该函数根据指定的列名将DataFrame划分为多个基于列名的小块数据。此方法返回两个结果:第一个结果是一个由分组条件构成的元组;第二个结果则是经过分组处理后生成的原始DataFrame。
%%timeit -n 10
for group, frame in df.groupby('STNAME'):
avg = np.average(frame['CENSUS2010POP'])
print('Counties in state ' + group + ' have an average population of ' + str(avg))

该函数也可被用作groupby的一个参数。其效果等同于将所有数据通过该函数进行处理。举个例子来说,在面对海量数据时如果你想在限定时间内完成三分之一的数据处理工作,则可创建一个基于州首字母划分数据的函数:通过调用groupby方法并结合此函数来进行数据分割操作,在此之前需先对DataFrame设置相应的索引字段以便实现分组。

在一般情况下,在进行数据处理时会遵循以下工作流程:首先会对数据进行分割操作;随后对分割后的数据集执行相应的操作;最后再将处理后各部分进行整合汇总。这种操作模式被称为拆分-应用-组合(split-apply-combine)模式。我们来探讨如何利用该模式中的聚合功能:groupby对象具备一个名为agg的聚合方法;该方法的作用相当于aggregate(聚合)的意思。 agg方法能够对单个或多个字段的数据执行特定操作,并返回所需的结果。通过传递一个字典类型的参数配置这些选项
df = pd.read_csv('census.csv')
df = df[df['SUMLEV']==50]
df.groupby('STNAME').agg({'CENSUS2010POP': np.average})

当调用agg方法并传递一个字典参数时,该字典既可以用于指定输入数据的列名(即输入特征),也可以用于指定输出结果的列名(即目标特征)。值得注意的是,DataFrameGroupBy和SeriesGroupBy属于不同的数据类型,在执行agg操作时会产生不同的结果。

a.对SeriesGroupBy类型进行操作:
(df.set_index('STNAME').groupby(level=0)['CENSUS2010POP']
.agg({'avg': np.average, 'sum': np.sum}))
这段代码的作用是将该行数据按各州名称分别进行计算。由于仅包含一行数据, 因此average和sum函数均会应用于这一列. 其计算结果如图所示.

b.对DataFrameGroupBy类型进行操作:
(df.set_index('STNAME').groupby(level=0)['POPESTIMATE2010','POPESTIMATE2011']
.agg({'avg': np.average, 'sum': np.sum}))
这段代码用于处理POPESTIMATE2010和POPESTIMATE2011这两个数据字段,并执行平均值计算以及总和计算的任务。操作输出的具体信息如图所示。

但是,如果这么使用:
(df.set_index('STNAME').groupby(level=0)['POPESTIMATE2010','POPESTIMATE2011']
.agg({'POPESTIMATE2010': np.average, 'POPESTIMATE2011': np.sum}))
最后的结果是:

四、Scales
现在让我们深入分析一下数据的类型(type)和尺度(scale)。Pandas能够处理的数据类型已经被我们熟悉掌握了。然而需要注意的是,并非所有涉及的数据都属于这些计算类型的范畴。那么什么是度量标准呢?举个例子来说,在课程评价中常见的等级如A+、A等就构成了一个典型的度量系统。通过这样的分类体系我们可以更好地理解和分析各种各样的实际问题。在这一部分我们将详细阐述四种不同的度量体系:区间标度的数据、顺序标度的数据、名义标度的数据以及比率标度的数据。
比率尺度(Ratio Scale):其具体表现为数值形式,并且各相邻单位之间的间距相等,在这种尺度下可进行加法、减法、乘法和除法运算。包括但不限于身高和体重这样的指标
确定距离尺度(Interval Scale):即数字型变量的一种测度体系,在这种尺度下不仅可以进行加减运算以及计算平均值等统计操作,而且其数值特征也具有明确的数学意义。然而需要注意的是,并非所有的数字型变量都具备绝对零点这一特点。例如温度这一指标,在零度时并不代表完全没有热能。
定序型尺度(Ordinal Scale):具有固化的高低顺序特点,并通常以字符或数字的形式呈现。例如,在年龄变量中可用"A, B, C"分别代表"老"、“中"、“青"这三个类别,并且这些符号之间存在一定的顺序关系但彼此之间并非等距间隔因此既可以进行排序比较又不可以进行算术运算
定类型尺度(Nominal Scale):通常指的是类别形式,在这种情况下变量没有固有的大小或高低之分。比如男性标识为"男"和女性标识为"女"等

原因在于Pandas提供了一系列转换函数用于将数据从一个尺度转换到另一个尺度
1.Nominal Scale->Ordinal Scale
df = pd.DataFrame(['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D'],
index=['excellent', 'excellent', 'excellent', 'good', 'good', 'good', 'ok', 'ok', 'ok', 'poor', 'poor'])
df.rename(columns={0: 'Grades'}, inplace=True)
df
df['Grades'].astype('category').head()
这段代码随后生成了一个存储成绩的DataFrame,并利用astype()函数将其转换为Nominal Scale的数据类型

grades = df['Grades'].astype('category',
categories=['D', 'D+', 'C-', 'C', 'C+', 'B-', 'B', 'B+', 'A-', 'A', 'A+'],
ordered=True)
grades.head()
该代码通过指定astype函数中的order参数设为True值,实现了从Nominal Scale到Ordinal Scale的转换。

在pandas库中,默认将布尔值术语称为dummy variables,并且该库提供了一个名为get_dummies()的函数用于处理这些变量。
2.Ratio Scale->Nominal Scale:
我们采用Pandas库中的cut()函数来实现这一目标。该函数接受两个主要参数作为输入。其中第一个输入参数用于指定需要分割的数据集;第二个输入参数则为所划分的区间数量。例如,在数据分析中,我们可以将连续型变量划分为五分位数区间:Q1, Q2, Q3, Q4, Q5。
s = pd.Series([168, 180, 174, 190, 170, 185, 179, 181, 175, 169, 182, 177, 180, 171])
pd.cut(s, 3)
pd.cut(s, 3, labels=['Small', 'Medium', 'Large'])
pd.cut(s,3)的返回结果:实际上就是将该区间168~190划分为三等分,并确定每个数据所属的区间段。

该代码段的运行结果等价于将给定序列s划分为三个区间,并为每个区间分配对应的标签Small、Medium和Large这三个指定的标签类别。这相当于对给定序列s实施了一一对应关系的明确划分过程

五、Pivot Tables
该技术旨在通过聚合功能来支持特定的数据分析目标。该技术依赖于多个聚合函数来生成汇总结果,并自身作为一个高效的工具进行操作。

我们还可以应用多个函数:

六、Date Functionality in Pandas
1.Timestamp:
Timestamp表示的是单一的时间戳点。
其数值与其对应的时间点相关联。
通常情况下,Timestamp和Python的datetime是可以相互转换的。

2.Period:
Period代表的是一段时间,单个时间跨度,例如特定的日期或者月份。

3.DatetimeIndex:
Timestamp作为索引就被称为DatetimeIndex。例如:

4.PeriodIndex:
Period作为索引就被称为PeriodIndex。例如:

5.转换成Datetime
随后我们将这些日期以文本类型的表示方式进行生成,并将其设为数据框的索引。
d1 = ['2 June 2013', 'Aug 29, 2014', '2015-06-26', '7/12/16']
ts3 = pd.DataFrame(np.random.randint(10, 100, (4,2)), index=d1, columns=list('ab'))
ts3
通过观察结果可以看出,在数据处理过程中时间格式呈现为多种多样化的形式。我们可以通过借助于pandas库中的to_datetime函数将这些时间格式转换为统一的标准形式。
ts3.index = pd.to_datetime(ts3.index)

6.Timedeltas:
Timedeltas是指时间差。

7.Working with Dates in a DateFrame:
假设我们需要查看一组数据。这些数据是从2016年10月开始每隔两周在周日进行测量所得的共9个样本点。为了方便分析我们将利用pandas库中的date_range函数生成一个满足条件的时间索引对象随后在每个时间点上我们为时间索引生成一些随机数值以便后续的数据处理工作更好地开展。具体的代码实现将依此展开
dates = pd.date_range('10-01-2016', periods=9, freq='2W-SUN')
df = pd.DataFrame({'Count 1': 100 + np.random.randint(-5, 10, 9).cumsum(),
'Count 2': 120 + np.random.randint(-5, 10, 9)}, index=dates)
df

通过weekday_name属性,我们可以查看每个特定的日期是星期几。

通过diff()函数,我们可以查看每个日期对应的值间的差值:

通过resample函数能得到每个月的平均值:

直接使用方括号能根据给定的年、月、日进行查找,甚至能进行分片:

通过调用asfreq()方法可以修改DataFrame中日期频率。举个例子来说,在处理df对象时想要将日期频率调整为每周固定在周日,则可以调用该方法并对其进行相应设置。具体来说,在第一个参数位置指定为'W'即每周一次,并选择前向填充策略作为method参数值来填充缺失的数据点。

