Pandas的使用(下)
在这一章,我们接着介绍Pandas的基本使用方法。基于上一章介绍的Pandas的基本操作,我们尝试进行一些实际应用中经常会用到的数据处理步骤,例如缺失值检测和处理、数据格式的转换、数据集的合并和分组计算等。这一章使用的例子都是最简单的情况,在实际应用中我们可能会遇到更加复杂的需求,会要求我们能够更加灵活地运用这些基本数据处理技术,将困难的问题拆解为标准的情况进行处理。
1 数据清洗和准备
首先仍然是导入我们需要用到的Pandas和NumPy:
import pandas as pd import numpy as np
1.1 处理缺失数据
1.1.1 判断缺失数据
Pandas将缺失值统称为NA(not available),包括NaN(not a number)和None。回顾一下,NaN是我们在NumPy中见到的概念,float类型,表达的是"虽然在概念上是数据,但不是数字”的概念,例如我们除以0的时候就会得到一个NaN;而None是Python原生的基础类型之一,表示空。
我们可以用isnull来判断是否是缺失数据:
obj = pd.Series([0, '1', None, np.nan]) print(obj.isnull())
输出为:
0 False 1 False 2 True 3 True dtype: bool
1.1.2 滤除缺失数据
Pandas提供了dropna函数来过滤掉那些缺失的数据。对于一个Series,dropna返回一个仅含非空数据和索引值的Series:
obj = pd.Series([0, '1', None, np.nan]) print(obj.dropna()) print() print(obj) # 返回了一个新的对象,obj本身没有变化
输出为:
0 0 1 1 dtype: object 0 0 1 1 2 None 3 NaN dtype: object
而对于DataFrame对象,dropna默认丢弃任何含有缺失值的行,我们也可以通过axis=1指明丢弃任何含有缺失值的列:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.dropna()) print() print(df.dropna(axis=1))
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 1.0 6.5 3.0 Empty DataFrame Columns: [] Index: [0, 1, 2, 3]
如果只想丢弃全部为NA的行(或者列),我们可以指明参数how='all':
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.dropna(how='all'))
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 3 NaN 6.5 3.0
另外我们也可以用thresh参数来指明最多删除几行:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.dropna()) print() print(df.dropna(thresh=1))
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 1.0 6.5 3.0 0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 3 NaN 6.5 3.0
1.1.3 填充缺失数据
我们可以调用fillna函数来填充缺失数据,例如将所有缺失值填充为一个常数:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.fillna(0))
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 1.0 6.5 3.0 1 1.0 0.0 0.0 2 0.0 0.0 0.0 3 0.0 6.5 3.0
如果我们通过一个字典进行填充,就可以实现对不同的列填充不同的值:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.fillna({0: 0, 1: 1, 2: 2}))
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 1.0 6.5 3.0 1 1.0 1.0 2.0 2 0.0 1.0 2.0 3 0.0 6.5 3.0
如果我们不想返回新的对象,而是“原地”修改df,可以指明参数inplace=True:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() df.fillna(0, inplace=True) print(df) # df被修改了
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 1.0 6.5 3.0 1 1.0 0.0 0.0 2 0.0 0.0 0.0 3 0.0 6.5 3.0
我们也可以通过method='ffill'来指明我们用上面一行的值来进行填充:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.fillna(method='ffill')) # 我们会看到有一些值被“复制”了多次,我们可以通过limit来加一个限制 print() print(df.fillna(method='ffill', limit=1))
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 1.0 6.5 3.0 1 1.0 6.5 3.0 2 1.0 6.5 3.0 3 1.0 6.5 3.0 0 1 2 0 1.0 6.5 3.0 1 1.0 6.5 3.0 2 1.0 NaN NaN 3 NaN 6.5 3.0
另外一种常见情况是我们想要用其余数据的平均值来填充缺失值。对于Series而言,我们可以这样操作:
obj = pd.Series([1., np.nan, 2., np.nan]) print(obj.fillna(obj.mean()))
输出为:
0 1.0 1 1.5 2 2.0 3 1.5 dtype: float64
对于DataFrame,我们可以通过某一列的平均值来填充这一列的缺失值:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.fillna(df.mean())) # 默认情况按列的平均值填充
输出为:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.fillna(df.mean())) # 默认情况按列的平均值填充 df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.fillna(df.mean())) # 默认情况按列的平均值填充 0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 1.0 6.5 3.0 1 1.0 6.5 3.0 2 1.0 6.5 3.0 3 1.0 6.5 3.0
1.2 数据转换
1.2.1 移除重复数据
有时候由于数据收集过程中的问题,我们会遇到重复数据的情况。我们可以用duplicated来检查各行是之前已经出现过的重复行:
df = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]}) print(df) print() print(df.duplicated())
输出为:
k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 5 two 4 6 two 4 0 False 1 False 2 False 3 False 4 False 5 False 6 True dtype: bool
要移除这些重复的数据,可以用drop_duplicates,它会返回一个新的DataFrame:
df = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]}) print(df) print() print(df.drop_duplicates())
输出为:
k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 5 two 4 6 two 4 k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 5 two 4
duplicated和drop_duplicates都默认会判断全部列,只有当全部列的值都是一样的,才认为是重复的。不过我们可以指定部分列进行重复项判断:
df = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]}) print(df) print() print(df.drop_duplicates(['k1'])) # 例如这里我们只依据k1这一列
输出为:
k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 5 two 4 6 two 4 k1 k2 0 one 1 1 two 1
另外,duplicated和drop_duplicates默认保留的是第一个出现的值组合。传入keep='last'则保留最后一个:
df = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]}) print(df) print() print(df.drop_duplicates(keep='last'))
输出为:
k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 5 two 4 6 two 4 k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 6 two 4
1.2.2 利用函数或映射进行数据转换
有些时候我们希望根据数组、Series或DataFrame列中的值来实现转换工作,这个时候就需要用到map函数,并给它一个函数或含有映射关系的字典型对象作为我们转换的规则:
df = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]}) print(df) print() map_to_k3 = {'one': 1, 'two': 2} # 定义一个字典作为映射的规则,one->1, two->2 df['k3'] = df['k1'].map(map_to_k3) # 对df['k1']这个Series应用我们的映射规则,并得到一个新的Series作为我们的k3这一列 print(df)
输出为:
k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 5 two 4 6 two 4 k1 k2 k3 0 one 1 1 1 two 1 2 2 one 2 1 3 two 3 2 4 one 3 1 5 two 4 2 6 two 4 2
我们也可以定义一个lambda函数来实现相同的功能:
df = pd.DataFrame({'k1': ['one', 'two'] * 3 + ['two'], 'k2': [1, 1, 2, 3, 3, 4, 4]}) print(df) print() map_to_k3 = {'one': 1, 'two': 2} # 定义一个字典作为映射的规则,one->1, two->2 map_func = lambda x:map_to_k3[x] # map_func的作用就是字典中按键取值的操作 df['k3'] = df['k1'].map(map_func) # 对df['k1']这个Series应用我们的映射规则,并得到一个新的Series作为我们的k3这一列 print(df)
输出为:
k1 k2 0 one 1 1 two 1 2 one 2 3 two 3 4 one 3 5 two 4 6 two 4 k1 k2 k3 0 one 1 1 1 two 1 2 2 one 2 1 3 two 3 2 4 one 3 1 5 two 4 2 6 two 4 2
1.2.3 替换值
有些时候我们想要把Series或者DataFrame中的某个值换成另一个值,一个特例是我们之前fillna将NA值替换成某个具体数值的操作。更通用和灵活的方法是使用replace函数:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.replace(np.nan, 0)) # 我们要把np.nan换成0
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 1.0 6.5 3.0 1 1.0 0.0 0.0 2 0.0 0.0 0.0 3 0.0 6.5 3.0
如果我们希望一次性替换多个值,可以传入一个由待替换值组成的列表以及一个替换值:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.replace([np.nan, 1.0], 0)) # 我们要把np.nan和1.0都换成0
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 0.0 6.5 3.0 1 0.0 0.0 0.0 2 0.0 0.0 0.0 3 0.0 6.5 3.0
如果要让每个值有不同的替换值,可以传递一个替换列表:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.replace([np.nan, 1.0], [0, -1])) # 我们要把np.nan换成0,把1.0换成-1.0
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 -1.0 6.5 3.0 1 -1.0 0.0 0.0 2 0.0 0.0 0.0 3 0.0 6.5 3.0
传入的参数也可以是字典:
df = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan], [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]]) print(df) print() print(df.replace({np.nan:0, 1.0:-0.1})) # 我们要把np.nan换成0,把1.0换成-1.0
输出为:
0 1 2 0 1.0 6.5 3.0 1 1.0 NaN NaN 2 NaN NaN NaN 3 NaN 6.5 3.0 0 1 2 0 -0.1 6.5 3.0 1 -0.1 0.0 0.0 2 0.0 0.0 0.0 3 0.0 6.5 3.0
1.2.4 重命名轴索引
我们前面提过了Series或DataFrame的索引都可以通过reindex函数直接进行重新索引。而我们也可以跟Series中的值一样,通过函数或映射进行转换,从而得到一个新的索引:
df = pd.DataFrame(np.arange(9).reshape((3, 3)), index=['a', 'c', 'd'], columns=['Ohio', 'Texas', 'California']) print(df) map_func = lambda x:x.upper() # 例如我们要把索引值都改成大写,直接调用字符串的upper函数 df.index = df.index.map(map_func) # 直接对DataFrame的索引对象应用map_func print(df)
输出为:
Ohio Texas California a 0 1 2 c 3 4 5 d 6 7 8 Ohio Texas California A 0 1 2 C 3 4 5 D 6 7 8
1.2.5 离散化和面元划分
连续数据常常被离散化或拆分为“面元”(bin)。假设有一组人员数据,我们希望将它们划分为不同的年龄组:“18到25”、“26到35”、“35到60”以及“60以上”几个面元。要实现该功能,我们需要使用pandas的cut函数:
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32] bins = [18, 25, 35, 60, 100] cats = pd.cut(ages, bins) # 传入数据和我们用来分隔的区间 print(cats)
输出为:
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]] Length: 12 Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
Pandas返回的是一个特殊的Categorical对象。结果展示了pandas.cut划分的面元。我们可以查看分隔之后的结果:
print(cats.codes) # 查看每个数据点究竟属于哪个区间 print() print(cats.categories) # 查看所有区间 print() print(pd.value_counts(cats)) # 用pandas的value_counts函数统计每一个区间中的元素个数
输出为:
[0 0 1 1 0 1 2 2 3 3 3 2] IntervalIndex([(19.999, 22.75], (22.75, 29.0], (29.0, 38.0], (38.0, 61.0]], closed='right', dtype='interval[float64]') (38.0, 61.0] 3 (29.0, 38.0] 3 (22.75, 29.0] 3 (19.999, 22.75] 3 dtype: int64
跟“区间”的数学符号一样,圆括号表示开端,而方括号则表示闭端(包括)。哪边是闭端可以通过right=False进行修改:
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32] bins = [18, 25, 35, 60, 100] cats = pd.cut(ages, bins, right=False) print(cats.categories)
输出为:
IntervalIndex([[18, 25), [25, 35), [35, 60), [60, 100)], closed='left', dtype='interval[int64]')
qcut是一个非常类似于cut的函数,它可以根据样本分位数对数据进行面元划分。根据数据的分布情况,cut可能无法使各个面元中含有相同数量的数据点。而qcut由于使用的是样本分位数,因此可以得到大小基本相等的面元:
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32] cats = pd.qcut(ages, 4) # 我们要把ages大致分成4组,使得4组包含的元素个数差不多 print(cats.categories) # qcut会自动计算区间如何划分 print() print(pd.value_counts(cats))
输出为:
IntervalIndex([(19.999, 22.75], (22.75, 29.0], (29.0, 38.0], (38.0, 61.0]], closed='right', dtype='interval[float64]') (38.0, 61.0] 3 (29.0, 38.0] 3 (22.75, 29.0] 3 (19.999, 22.75] 3 dtype: int64
1.2.6 检测和过滤异常值
我们可以通过一些条件来检测和过滤掉一些异常值,例如特别大或者特别小的值:
df = pd.DataFrame([[1, 3, 2, 4], [1, 1, 1, 1], [-1, -2, -4, -4]]) print(df) print() print(df[2][np.abs(df[2]) > 3]) # 找到某列中绝对值大小超过3的值 print() print(df[(np.abs(df) > 3).any(1)]) # 用any函数,只要有一列绝对值大小超过3,就把这一行找出来
输出为:
0 1 2 3 0 1 3 2 4 1 1 1 1 1 2 -1 -2 -4 -4 2 -4 Name: 2, dtype: int64 0 1 2 3 0 1 3 2 4 2 -1 -2 -4 -4
找到这些值之后,我们可以用赋值的方式将它们限制在一个范围之内:
df[np.abs(df) > 3] = np.sign(df) * 3 # sign会给出这个数的正负号 print(df)
输出为:
0 1 2 3 0 1 3 2 3 1 1 1 1 1 2 -1 -2 -3 -3
1.2.7 排列和随机采样
要对Series或者DataFrame中的数据进行重新排列,我们可以使用np.random.permutation函数,生成一个表示新顺序的整数数组,然后利用iloc的索引操作,或者是take函数:
df = pd.DataFrame([[1, 3, 2, 4], [1, 1, 1, 1], [-1, -2, -4, -4]]) print(df) print() sampler = np.random.permutation(3) print(df.iloc[sampler]) print() print(df.take(sampler))
输出为:
0 1 2 3 0 1 3 2 4 1 1 1 1 1 2 -1 -2 -4 -4 0 1 2 3 0 1 3 2 4 1 1 1 1 1 2 -1 -2 -4 -4 0 1 2 3 0 1 3 2 4 1 1 1 1 1 2 -1 -2 -4 -4
我们也可以使用sample函数进行采样。指明replace=True可以允许重复选择,即有放回的采样。
df = pd.DataFrame([[1, 3, 2, 4], [1, 1, 1, 1], [-1, -2, -4, -4]]) print(df) print() print(df.sample(3)) print() print(df.sample(10, replace=True)) # print(df.sample(10)) # 如果不指明replace=True,并尝试采样的数量超过数据总量时会报错
输出为:
0 1 2 3 0 1 3 2 4 1 1 1 1 1 2 -1 -2 -4 -4 0 1 2 3 1 1 1 1 1 0 1 3 2 4 2 -1 -2 -4 -4 0 1 2 3 0 1 3 2 4 0 1 3 2 4 2 -1 -2 -4 -4 2 -1 -2 -4 -4 0 1 3 2 4 1 1 1 1 1 0 1 3 2 4 2 -1 -2 -4 -4 1 1 1 1 1 1 1 1 1 1
2 层次化索引
层次化索引(hierarchical indexing)是Pandas的一项重要功能,它使我们能在一个轴上拥有多个(两个以上)索引级别。
data = pd.Series(np.random.randn(9), index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'], [1, 2, 3, 1, 3, 1, 2, 2, 3]]) # 传入一个列表的列表,作为两层的索引 print(data) print() print(data.index)
输出为:
a 1 -1.648029 2 0.420233 3 1.034082 b 1 0.758560 3 -1.871431 c 1 0.107052 2 1.128419 d 2 0.601011 3 0.984184 dtype: float64 MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]], codes=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])
我们在选取了外层的数据之后,还可以在内层继续选取,或者直接按内层索引进行选取:
print(data['b'][3]) # 外层索引用'b',内层索引用3 print() print(data.loc[:, 2]) # 直接选取内层索引为2的数据
输出为:
-1.2163750584353654 a -0.709735 c 0.993300 d -0.152503 dtype: float64
通过unstack函数,我们可以把这种两层索引的Series重新安排到一个DataFrame中,而stack刚好是相反的操作:
print(data.unstack()) print() print(data.unstack().stack())
输出为:
1 2 3 a 0.548756 -0.709735 2.586256 b 0.107833 NaN -1.216375 c -0.195742 0.993300 NaN d NaN -0.152503 -0.217671 a 1 0.548756 2 -0.709735 3 2.586256 b 1 0.107833 3 -1.216375 c 1 -0.195742 2 0.993300 d 2 -0.152503 3 -0.217671 dtype: float64
对于一个DataFrame,每条轴都可以有分层索引:
frame = pd.DataFrame(np.arange(12).reshape((4, 3)), index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]], columns=[['Ohio', 'Ohio', 'Colorado'], ['Green', 'Red', 'Green']]) print(frame)
输出为:
Ohio Colorado Green Red Green a 1 0 1 2 2 3 4 5 b 1 6 7 8 2 9 10 11
每层索引都可以指明名字:
frame.index.names = ['key1', 'key2'] frame.columns.names = ['state', 'color'] print(frame)
输出为:
state Ohio Colorado color Green Red Green key1 key2 a 1 0 1 2 2 3 4 5 b 1 6 7 8 2 9 10 11
3 合并数据集
Pandas提供了merge,join,concat等函数将不同的数据集组合成一个数据集。
3.1 concat
concat可以沿着一条轴将多个对象堆叠到一起。默认情况是按列推叠,相同的列的数据会被推到一起:
df1 = pd.DataFrame({'A': ['A0', 'A1', 'A2', 'A3'], 'B': ['B0', 'B1', 'B2', 'B3'], 'C': ['C0', 'C1', 'C2', 'C3'], 'D': ['D0', 'D1', 'D2', 'D3']}, index=[0, 1, 2, 3]) df2 = pd.DataFrame({'A': ['A4', 'A5', 'A6', 'A7'], 'B': ['B4', 'B5', 'B6', 'B7'], 'C': ['C4', 'C5', 'C6', 'C7'], 'D': ['D4', 'D5', 'D6', 'D7']}, index=[4, 5, 6, 7]) df3 = pd.DataFrame({'A': ['A8', 'A9', 'A10', 'A11'], 'B': ['B8', 'B9', 'B10', 'B11'], 'C': ['C8', 'C9', 'C10', 'C11'], 'D': ['D8', 'D9', 'D10', 'D11']}, index=[8, 9, 10, 11]) frames = [df1, df2, df3] result = pd.concat(frames) print(result)
输出为:
A B C D 0 A0 B0 C0 D0 1 A1 B1 C1 D1 2 A2 B2 C2 D2 3 A3 B3 C3 D3 4 A4 B4 C4 D4 5 A5 B5 C5 D5 6 A6 B6 C6 D6 7 A7 B7 C7 D7 8 A8 B8 C8 D8 9 A9 B9 C9 D9 10 A10 B10 C10 D10 11 A11 B11 C11 D11
我们可以通过keys参数指明外层的索引:
result = pd.concat(frames, keys=['x', 'y', 'z']) print(result)
输出为:
A B C D x 0 A0 B0 C0 D0 1 A1 B1 C1 D1 2 A2 B2 C2 D2 3 A3 B3 C3 D3 y 4 A4 B4 C4 D4 5 A5 B5 C5 D5 6 A6 B6 C6 D6 7 A7 B7 C7 D7 z 8 A8 B8 C8 D8 9 A9 B9 C9 D9 10 A10 B10 C10 D10 11 A11 B11 C11 D11
或者我们可以通过传入一个字典的形式实现上面一样的功能:
pieces = {'x': df1, 'y': df2, 'z': df3} result = pd.concat(pieces) print(result)
输出为:
A B C D x 0 A0 B0 C0 D0 1 A1 B1 C1 D1 2 A2 B2 C2 D2 3 A3 B3 C3 D3 y 4 A4 B4 C4 D4 5 A5 B5 C5 D5 6 A6 B6 C6 D6 7 A7 B7 C7 D7 z 8 A8 B8 C8 D8 9 A9 B9 C9 D9 10 A10 B10 C10 D10 11 A11 B11 C11 D11
这样我们可以比较方便地取回对应原来数据集的部分:
print(result.loc['y'])
输出为:
A B C D 4 A4 B4 C4 D4 5 A5 B5 C5 D5 6 A6 B6 C6 D6 7 A7 B7 C7 D7
我们也可以通过axis指明推叠方向:
df4 = pd.DataFrame({'B': ['B2', 'B3', 'B6', 'B7'], 'D': ['D2', 'D3', 'D6', 'D7'], 'F': ['F2', 'F3', 'F6', 'F7']}, index=[2, 3, 6, 7]) result = pd.concat([df1, df4], axis=1) # 指明按行进行推叠 print(result)
输出为:
A B C D B D F 0 A0 B0 C0 D0 NaN NaN NaN 1 A1 B1 C1 D1 NaN NaN NaN 2 A2 B2 C2 D2 B2 D2 F2 3 A3 B3 C3 D3 B3 D3 F3 6 NaN NaN NaN NaN B6 D6 F6 7 NaN NaN NaN NaN B7 D7 F7
在上面的默认情况下,join的方式被指定为outer(并集),也就是说只要df1或者df4中有的行索引都会被保留下来,我们也可以指明join='inner'(交集)来仅仅保留df1和df4中都有的行索引:
result = pd.concat([df1, df4], axis=1, join='inner') print(result)
输出为:
A B C D B D F 2 A2 B2 C2 D2 B2 D2 F2 3 A3 B3 C3 D3 B3 D3 F3
或者我们指明使用df1的行索引:
result = pd.concat([df1, df4], axis=1, join_axes=[df1.index]) print(result)
输出为:
A B C D B D F 0 A0 B0 C0 D0 NaN NaN NaN 1 A1 B1 C1 D1 NaN NaN NaN 2 A2 B2 C2 D2 B2 D2 F2 3 A3 B3 C3 D3 B3 D3 F3
append函数的功能和concat相同,但是append是应用在某一个Series或DataFrame上的:
result = df1.append(df2) print(result)
输出为:
A B C D 0 A0 B0 C0 D0 1 A1 B1 C1 D1 2 A2 B2 C2 D2 3 A3 B3 C3 D3 4 A4 B4 C4 D4 5 A5 B5 C5 D5 6 A6 B6 C6 D6 7 A7 B7 C7 D7
在应用append时,两个DataFrame的列不一定要完全相同:
result = df1.append(df4) print(result)
输出为:
A B C D F 0 A0 B0 C0 D0 NaN 1 A1 B1 C1 D1 NaN 2 A2 B2 C2 D2 NaN 3 A3 B3 C3 D3 NaN 2 NaN B2 NaN D2 F2 3 NaN B3 NaN D3 F3 6 NaN B6 NaN D6 F6 7 NaN B7 NaN D7 F7
我们也可以在append中使用列表,同时推叠多个DataFrame:
result = df1.append([df2, df3]) print(result)
输出为:
A B C D 0 A0 B0 C0 D0 1 A1 B1 C1 D1 2 A2 B2 C2 D2 3 A3 B3 C3 D3 4 A4 B4 C4 D4 5 A5 B5 C5 D5 6 A6 B6 C6 D6 7 A7 B7 C7 D7 8 A8 B8 C8 D8 9 A9 B9 C9 D9 10 A10 B10 C10 D10 11 A11 B11 C11 D11
如果我们不想保留原来的DataFrame的行索引,我们可以在concat函数或append函数中指明ignore_index=True:
result = pd.concat([df1, df4], ignore_index=True) print(result)
输出为:
A B C D F 0 A0 B0 C0 D0 NaN 1 A1 B1 C1 D1 NaN 2 A2 B2 C2 D2 NaN 3 A3 B3 C3 D3 NaN 4 NaN B2 NaN D2 F2 5 NaN B3 NaN D3 F3 6 NaN B6 NaN D6 F6 7 NaN B7 NaN D7 F7
我们也可以拼接一个DataFrame和一个Series,在拼接的过程中,Series会被自动转成DataFrame:
s1 = pd.Series(['X0', 'X1', 'X2', 'X3'], name='X') result = pd.concat([df1, s1], axis=1) print(result)
输出为:
A B C D X 0 A0 B0 C0 D0 X0 1 A1 B1 C1 D1 X1 2 A2 B2 C2 D2 X2 3 A3 B3 C3 D3 X3
如果Series没有指明name,那么列名会被自动计数填充:
s2 = pd.Series(['_0', '_1', '_2', '_3']) result = pd.concat([df1, s2, s2, s2], axis=1) print(result)
输出为:
A B C D 0 1 2 0 A0 B0 C0 D0 _0 _0 _0 1 A1 B1 C1 D1 _1 _1 _1 2 A2 B2 C2 D2 _2 _2 _2 3 A3 B3 C3 D3 _3 _3 _3
如果指明参数ignore_index=True,那么所有的列名都会被重新编号:
result = pd.concat([df1, s1], axis=1, ignore_index=True) print(result)
输出为:
0 1 2 3 4 0 A0 B0 C0 D0 X0 1 A1 B1 C1 D1 X1 2 A2 B2 C2 D2 X2 3 A3 B3 C3 D3 X3
我们也可以通过append函数将一个Series作为新的一行加到一个DataFrame中,在这种情况下我们一般会指明ignore_index=True:
s2 = pd.Series(['X0', 'X1', 'X2', 'X3'], index=['A', 'B', 'C', 'D']) result = df1.append(s2, ignore_index=True) print(result)
输出为:
A B C D 0 A0 B0 C0 D0 1 A1 B1 C1 D1 2 A2 B2 C2 D2 3 A3 B3 C3 D3 4 X0 X1 X2 X3
3.2 merge
merge可以根据一个或多个键将不同DataFrame中的行连接起来,来看具体的例子:
left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'], 'A': ['A0', 'A1', 'A2', 'A3'], 'B': ['B0', 'B1', 'B2', 'B3']}) right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'], 'C': ['C0', 'C1', 'C2', 'C3'], 'D': ['D0', 'D1', 'D2', 'D3']}) result = pd.merge(left, right, on='key') # 参数on指明了我们要进行连接的依据 print(result)
输出为:
key A B C D 0 K0 A0 B0 C0 D0 1 K1 A1 B1 C1 D1 2 K2 A2 B2 C2 D2 3 K3 A3 B3 C3 D3
在上面的例子中,left和right的具有相同key的行会被连接成result中的一行。我们也可以在参数on中指明多列作为连接的依据,默认情况下会采取how='inner',即取交集的方式:
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'], 'key2': ['K0', 'K1', 'K0', 'K1'], 'A': ['A0', 'A1', 'A2', 'A3'], 'B': ['B0', 'B1', 'B2', 'B3']}) right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'], 'key2': ['K0', 'K0', 'K0', 'K0'], 'C': ['C0', 'C1', 'C2', 'C3'], 'D': ['D0', 'D1', 'D2', 'D3']}) result = pd.merge(left, right, on=['key1', 'key2']) print(result)
输出为:
key1 key2 A B C D 0 K0 K0 A0 B0 C0 D0 1 K1 K0 A2 B2 C1 D1 2 K1 K0 A2 B2 C2 D2
我们也可以修改控制连接方式的参数how为outer(并集),left(用第一个DataFrame中的数值),right(用第二个DataFrame中的数值):
result = pd.merge(left, right, how='left', on=['key1', 'key2']) print(result)
输出为:
key1 key2 A B C D 0 K0 K0 A0 B0 C0 D0 1 K0 K1 A1 B1 NaN NaN 2 K1 K0 A2 B2 C1 D1 3 K1 K0 A2 B2 C2 D2 4 K2 K1 A3 B3 NaN NaN
result = pd.merge(left, right, how='right', on=['key1', 'key2']) print(result)
输出为:
key1 key2 A B C D 0 K0 K0 A0 B0 C0 D0 1 K1 K0 A2 B2 C1 D1 2 K1 K0 A2 B2 C2 D2 3 K2 K0 NaN NaN C3 D3
result = pd.merge(left, right, how='outer', on=['key1', 'key2']) print(result)
输出为:
key1 key2 A B C D 0 K0 K0 A0 B0 C0 D0 1 K0 K1 A1 B1 NaN NaN 2 K1 K0 A2 B2 C1 D1 3 K1 K0 A2 B2 C2 D2 4 K2 K1 A3 B3 NaN NaN 5 K2 K0 NaN NaN C3 D3
如果left和right这两个DataFrame除了用来连接的列之外,还有相同的列的时候,pandas会自动地在result中把这些列加以区分:
left = pd.DataFrame({'A' : [1,2], 'B' : [2, 2]}) right = pd.DataFrame({'A' : [4,5,6], 'B': [2,2,2]}) result = pd.merge(left, right, on='B', how='outer') # left和right的列都是A和B,但是在连接时只使用B作为依据 print(result)
输出为:
A_x B A_y 0 1 2 4 1 1 2 5 2 1 2 6 3 2 2 4 4 2 2 5 5 2 2 6
如果我们不想采用这种默认的区分方式,也可以自己指明后缀suffixes:
left = pd.DataFrame({'A' : [1,2], 'B' : [2, 2]}) right = pd.DataFrame({'A' : [4,5,6], 'B': [2,2,2]}) result = pd.merge(left, right, on='B', how='outer', suffixes=['_left', '_right']) print(result)
输出为:
A_left B A_right 0 1 2 4 1 1 2 5 2 1 2 6 3 2 2 4 4 2 2 5 5 2 2 6
3.3 join
join可以按照行索引,将两个DataFrame连接成一个,有点类似merge,但是默认的连接方式是how='left':
left = pd.DataFrame({'A': ['A0', 'A1', 'A2'], 'B': ['B0', 'B1', 'B2']}, index=['K0', 'K1', 'K2']) right = pd.DataFrame({'C': ['C0', 'C2', 'C3'], 'D': ['D0', 'D2', 'D3']}, index=['K0', 'K2', 'K3']) result = left.join(right) # 用left去join right print(result) print() # 用merge其实也能实现相同的功能,不过比较繁琐,不指明参数on,而是指明left_index=True, right_index=True来说明我们要按照行索引进行连接 result = pd.merge(left, right, left_index=True, right_index=True, how='left') print(result)
输出为:
A B C D K0 A0 B0 C0 D0 K1 A1 B1 NaN NaN K2 A2 B2 C2 D2 A B C D K0 A0 B0 C0 D0 K1 A1 B1 NaN NaN K2 A2 B2 C2 D2
在join中,我们也可以指明连接方式为inner或outer:
result = left.join(right, how='inner') print(result)
输出为:
A B C D K0 A0 B0 C0 D0 K2 A2 B2 C2 D2
result = left.join(right, how='outer') print(result)
输出为:
A B C D K0 A0 B0 C0 D0 K1 A1 B1 NaN NaN K2 A2 B2 C2 D2 K3 NaN NaN C3 D3
当left和right出现重复的列时,join会要求我们指明后缀,来区分来自不同DataFrame中的相同的列:
left = pd.DataFrame({'A': ['A0', 'A1', 'A2'], 'B': ['B0', 'B1', 'B2']}, index=['K0', 'K1', 'K2']) right = pd.DataFrame({'A': ['C0', 'C2', 'C3'], 'B': ['D0', 'D2', 'D3']}, index=['K0', 'K2', 'K3']) # result = left.join(right) # 直接join会报错 result = left.join(right, lsuffix='_left', rsuffix='_right') # 指明后缀 print(result)
输出为:
A_left B_left A_right B_right K0 A0 B0 C0 D0 K1 A1 B1 NaN NaN K2 A2 B2 C2 D2
4 分组计算
4.1 GroupBy机制
pandas利用GroupBy机制来实现分组计算,具体的过程包括“split-apply-combine”(拆分-应用-合并):
- pandas对象(包括Series和DataFrame)按照某个键被拆分(split)为多组,例如按照行索引或列索引;
- 然后,我们可以将一个函数应用(apply)到各个分组并产生一个新值;
- 最后,所有这些函数的结果会被合并(combine)到最终的结果对象中。
整个流程可以用下图说明:
我们来看具体的例子。对于一个DataFrame,我们想要按照key1分组,然后计算data1列的平均值:
df = pd.DataFrame({'key1': ['a', 'a', 'b', 'b', 'a'], 'key2': ['one', 'two', 'one', 'two', 'one'], 'data1': np.random.randn(5), 'data2': np.random.randn(5)}) print(df) print() grouped = df['data1'].groupby(df['key1']) # 对data1按照key1进行分组 print(grouped) # 得到一个GroupBy对象,此时只是中间数据,还没有进行任何计算 print() print(grouped.mean()) # 然后我们调用平均值函数,就可以得到分组平均值
输出为:
key1 key2 data1 data2 0 a one -0.644855 -1.171283 1 a two 0.557939 -2.207411 2 b one 1.008353 1.138036 3 b two 1.132750 0.189582 4 a one 0.223394 -0.318009 <pandas.core.groupby.generic.SeriesGroupBy object at 0x0000026FE4313D68> key1 a 0.045493 b 1.070552 Name: data1, dtype: float64
如果我们通过两个键进行分组,就会得到一个层次化索引的Series:
means = df['data1'].groupby([df['key1'], df['key2']]).mean() # 通过key1和key2进行分组 print(means)
输出为:
key1 key2 a one -0.210730 two 0.557939 b one 1.008353 two 1.132750 Name: data1, dtype: float64
4.2 对分组进行迭代
GroupBy对象支持迭代,可以产生一组二元元组(由分组名和数据块组成):
print(df) # 用之前定义好的DataFrame print() for name, group in df.groupby('key1'): print(name) print(group)
输出为:
key1 key2 data1 data2 0 a one -0.644855 -1.171283 1 a two 0.557939 -2.207411 2 b one 1.008353 1.138036 3 b two 1.132750 0.189582 4 a one 0.223394 -0.318009 a key1 key2 data1 data2 0 a one -0.644855 -1.171283 1 a two 0.557939 -2.207411 4 a one 0.223394 -0.318009 b key1 key2 data1 data2 2 b one 1.008353 1.138036 3 b two 1.132750 0.189582
我们也可以把上面的结果做成一个字典:
pieces = dict(list(df.groupby('key1'))) print(pieces['b'])
输出为:
key1 key2 data1 data2 2 b one 1.008353 1.138036 3 b two 1.132750 0.189582
对于多重键的情况,元组的第一个元素将会是由键值组成的元组:
for (k1, k2), group in df.groupby(['key1', 'key2']): print((k1, k2)) print(group)
输出为:
('a', 'one') key1 key2 data1 data2 0 a one -0.644855 -1.171283 4 a one 0.223394 -0.318009 ('a', 'two') key1 key2 data1 data2 1 a two 0.557939 -2.207411 ('b', 'one') key1 key2 data1 data2 2 b one 1.008353 1.138036 ('b', 'two') key1 key2 data1 data2 3 b two 1.13275 0.189582
4.3 选取一列或列的子集
对于由DataFrame产生的GroupBy对象,如果用一个(单个字符串)或一组(字符串数组)列名对其进行索引,就能实现选取部分列进行聚合的目的。
print(df.groupby('key1')['data1']) # 得到一个数据为Series的GroupBy对象 print() print(df.groupby('key1')[['data2']]) # 得到一个数据为DataFrame的GroupBy对象 print() print(df.groupby('key1')[['data2']].mean())
输出为:
<pandas.core.groupby.generic.SeriesGroupBy object at 0x0000026FE43F18D0> <pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000026FE43357F0> data2 key1 a -1.232234 b 0.663809
4.4 apply
除了上面看到的mean、sum等内置的函数,我们也可以定义自己的函数,然后用apply函数进行调用:
def f(x): return np.random.choice(x, 1) # 从一列中的每组数据中随机选一个作为结果 print(df.groupby('key1')['data1'].apply(f)) # 这里函数f接受的是一维的输入,因此我们取了data1这一列作为输入
输出为:
key1 a [0.22339406178719257] b [1.0083533433635783] Name: data1, dtype: object
如果我们要应用的函数f还有参数,我们也可以在apply函数中指明相对应的参数,它们会传递到函数f中:
def f(x, n=1): return np.random.choice(x, n) # 从一列中的每组数据中随机选n个作为结果 print(df.groupby('key1')['data1'].apply(f, n=2)) # 我们可以在apply中指明n=2,它会被自动地应用到函数f中
输出为:
key1 a [0.5579390947161788, 0.22339406178719257] b [1.1327497534971687, 1.1327497534971687] Name: data1, dtype: object
回顾之前我们提到的fillna的操作,我们也可以采用apply函数,实现按分组填充平均值的操作:
df = pd.DataFrame({'key1': ['a', 'a', 'b', 'b', 'a'], 'key2': ['one', 'two', 'one', 'two', 'one'], 'data1': [1, 2, 3, 4, 5], 'data2': [6, 7, 8, 9, 10]}) df.loc[1, 'data1'] = np.nan # 我们人为添加一些缺失值作为演示 df.loc[3, 'data2'] = np.nan print(df) print() print(df.fillna(df.mean())) # 直接调用fillna会填充全列的平均值
输出为:
key1 key2 data1 data2 0 a one 1.0 6.0 1 a two NaN 7.0 2 b one 3.0 8.0 3 b two 4.0 NaN 4 a one 5.0 10.0 key1 key2 data1 data2 0 a one 1.00 6.00 1 a two 3.25 7.00 2 b one 3.00 8.00 3 b two 4.00 7.75 4 a one 5.00 10.00
我们可以用分组平均值去填充NA:
fill_mean = lambda g: g.fillna(g.mean()) # 相当于我们把填充平均值的操作在每一个分组内的DataFrame上进行操作 print(df.groupby('key1').apply(fill_mean)) # 按照key1进行分组再填充 print() print(df.groupby('key2').apply(fill_mean)) # 换了分组的依据,填充的值也变得不一样
输出为:
key1 key2 data1 data2 key1 a 0 a one 1.0 6.0 1 a two 3.0 7.0 4 a one 5.0 10.0 b 2 b one 3.0 8.0 3 b two 4.0 8.0 key1 key2 data1 data2 key2 one 0 a one 1.0 6.0 2 b one 3.0 8.0 4 a one 5.0 10.0 two 1 a two 4.0 7.0 3 b two 4.0 7.0
小结
这一章介绍了Pandas在实际问题中的常见数据处理技术,这些技术构成了大型数据处理问题的基础,需要大家熟练掌握并且在以后的问题中灵活运用。