利用python进行数据分析(重点、易忘点)---第八章数据规整:聚合、合并和重塑
1.层次化索引
层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。抽象点说,它使你能以低维度形式处理高维度数据。我们先来看一个简单的例子:创建一个Series,并用一个由列表或数组组成的列表作为索引:
In [9]: 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]])
In [10]: data
Out[10]:
a 1 -0.204708
2 0.478943
3 -0.519439
b 1 -0.555730
3 1.965781
c 1 1.393406
2 0.092908
d 2 0.281746
3 0.769023
dtype: float64
stack:将数据的列“旋转”为行;stack是堆叠的意思,就是堆叠成很多行。
unstack:将数据的行“旋转”为列。
In [16]: data.unstack()
Out[16]:
1 2 3
a -0.204708 0.478943 -0.519439
b -0.555730 NaN 1.965781
c 1.393406 0.092908 NaN
d NaN 0.281746 0.769023
In [17]: data.unstack().stack()
Out[17]:
a 1 -0.204708
2 0.478943
3 -0.519439
b 1 -0.555730
3 1.965781
c 1 1.393406
2 0.092908
d 2 0.281746
3 0.769023
dtype: float64
对于一个DataFrame,每条轴都可以有分层索引:
In [18]: 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']])
In [19]: frame
Out[19]:
Ohio Colorado
Green Red Green
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11
各层都可以有名字(可以是字符串,也可以是别的Python对象)。如果指定了名称,它们就会显示在控制台输出中:
In [20]: frame.index.names = ['key1', 'key2']
In [21]: frame.columns.names = ['state', 'color']
In [22]: frame
Out[22]:
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
2.重拍与分级排序(swaplevel)
swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化).
In [24]: frame.swaplevel('key1', 'key2')
Out[24]:
state Ohio Colorado
color Green Red Green
key2 key1
1 a 0 1 2
2 a 3 4 5
1 b 6 7 8
2 b 9 10 11
sort_index配合level关键字和axis关键字,按指定行/列的某层索引来排序。level可以为数字或者层次名字。
In [26]: frame.swaplevel(0, 1).sort_index(level=0)
Out[26]:
state Ohio Colorado
color Green Red Green
key2 key1
1 a 0 1 2
b 6 7 8
2 a 3 4 5
b 9 10 11
3.根据级别汇总统计(sum配合axis和level)
许多对DataFrame和Series的描述和汇总统计都有一个level选项,它用于指定在某条轴上求和的级别。
In [28]: frame.sum(level='color', axis=1)
Out[28]:
color Green Red
key1 key2
a 1 2 1
2 8 4
b 1 14 7
2 20 10
4.使用DataFrame的列进行索引(set_index、reset_index)
DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame:
In [30]: frame
Out[30]:
a b c d
0 0 7 one 0
1 1 6 one 1
2 2 5 one 2
3 3 4 two 0
4 4 3 two 1
5 5 2 two 2
6 6 1 two 3
In [31]: frame2 = frame.set_index(['c', 'd'])
In [32]: frame2
Out[32]:
a b
c d
one 0 0 7
1 1 6
2 2 5
two 0 3 4
1 4 3
2 5 2
3 6 1
reset_index的功能跟set_index刚好相反,层次化索引的级别会被转移到列里面:
In [34]: frame2.reset_index()
Out[34]:
c d a b
0 one 0 0 7
1 one 1 1 6
2 one 2 2 5
3 two 0 3 4
4 two 1 4 3
5 two 2 5 2
6 two 3 6 1
5.数据库风格的DataFrame合并(pd.merge)***很重要***
类似于数据库里面的表连接(join),pandas这里用到的是merge(),举例介绍:
In [37]: df1
Out[37]:
data1 key
0 0 b
1 1 b
2 2 a
3 3 c
4 4 a
5 5 a
6 6 b
In [38]: df2
Out[38]:
data2 key
0 0 a
1 1 b
2 2 d
如果没有指定,merge就会将重叠列的列名当做键( on='key'可省略)。不过,最好明确指定一下:
In [40]: pd.merge(df1, df2, on='key')
Out[40]:
data1 key data2
0 0 b 1
1 1 b 1
2 6 b 1
3 2 a 0
4 4 a 0
5 5 a 0
如果两个对象的列名不同,也可以分别进行指定:
In [41]: df3 = pd.DataFrame({'lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
....: 'data1': range(7)})
In [42]: df4 = pd.DataFrame({'rkey': ['a', 'b', 'd'],
....: 'data2': range(3)})
In [43]: pd.merge(df3, df4, left_on='lkey', right_on='rkey')
Out[43]:
data1 lkey data2 rkey
0 0 b 1 b
1 1 b 1 b
2 6 b 1 b
3 2 a 0 a
4 4 a 0 a
5 5 a 0 a
默认情况下,merge做的是“内连接”;结果中的键是交集。根据关键字how的指定,其他方式还有”left”、”right”以及”outer”。外连接求取的是键的并集,组合了左连接和右连接的效果:
In [44]: pd.merge(df1, df2, how='outer')
Out[44]:
data1 key data2
0 0.0 b 1.0
1 1.0 b 1.0
2 6.0 b 1.0
3 2.0 a 0.0
4 4.0 a 0.0
5 5.0 a 0.0
6 3.0 c NaN
7 NaN d 2.0
要根据多个键进行合并,传入一个由列名组成的列表即可:
In [51]: left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],
....: 'key2': ['one', 'two', 'one'],
....: 'lval': [1, 2, 3]})
In [52]: right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
....: 'key2': ['one', 'one', 'one', 'two'],
....: 'rval': [4, 5, 6, 7]})
In [53]: pd.merge(left, right, on=['key1', 'key2'], how='outer')
Out[53]:
key1 key2 lval rval
0 foo one 1.0 4.0
1 foo one 1.0 5.0
2 foo two 2.0 NaN
3 bar one 3.0 6.0
4 bar two NaN 7.0
对于合并运算需要考虑的最后一个问题是对重复列名的处理,pandas默认在重复列后面加上“_x”、"_y"等。merge有一个更实用的suffixes选项,用于指定附加到左右两个DataFrame对象的重叠列名上的字符串:
In [54]: pd.merge(left, right, on='key1')
Out[54]:
key1 key2_x lval key2_y rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
In [55]: pd.merge(left, right, on='key1', suffixes=('_left', '_right'))
Out[55]:
key1 key2_left lval key2_right rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
6.索引上的合并(left_index=True, right_index=True,join)
有时候,DataFrame中的连接键位于其索引中。在这种情况下,你可以传入left_index=True或right_index=True(或两个都传)以说明索引应该被用作连接键:
In [58]: left1
Out[58]:
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5
In [59]: right1
Out[59]:
group_val
a 3.5
b 7.0
In [60]: pd.merge(left1, right1, left_on='key', right_index=True)
Out[60]:
key value group_val
0 a 0 3.5
2 a 2 3.5
3 a 3 3.5
1 b 1 7.0
4 b 4 7.0
区分于pd.merge()大概率是用几个DataFrame的某列合并(除去left_index=True这种情况),join实现的是索引合并:
DataFrame还有一个便捷的join实例方法,它能更为方便地实现按索引合并。它还可用于合并多个带有相同或相似索引的DataFrame对象,但要求没有重叠的列。
In [70]: left2
Out[70]:
Ohio Nevada
a 1.0 2.0
c 3.0 4.0
e 5.0 6.0
In [71]: right2
Out[71]:
Missouri Alabama
b 7.0 8.0
c 9.0 10.0
d 11.0 12.0
e 13.0 14.0
In [73]: left2.join(right2, how='outer')
Out[73]:
Ohio Nevada Missouri Alabama
a 1.0 2.0 NaN NaN
b NaN NaN 7.0 8.0
c 3.0 4.0 9.0 10.0
d NaN NaN 11.0 12.0
e 5.0 6.0 13.0 14.0
因为一些历史版本的遗留原因,DataFrame的join方法默认使用的是左连接,保留左边表的行索引。它还支持在调用的DataFrame的列上,连接传递的DataFrame索引:
In [74]: left1.join(right1, on='key')
Out[74]:
key value group_val
0 a 0 3.5
1 b 1 7.0
2 a 2 3.5
3 a 3 3.5
4 b 4 7.0
5 c 5 NaN
多表连接的一些语法:
left2.join([right2, another], how='outer')
df1.join(df2, how='inner').join(df3, how='inner')
7.轴向连接(pd.concat)
pd.concat适用于:1.用索引进行拼接;2.常配合axis=1来实现之前的pd.merge或是join的效果,axis=0只是把一个DataFrame放到另一个DataFrame的上面。
不过有个问题,参与连接的片段在结果中区分不开。假设你想要在连接轴上创建一个层次化索引。使用keys参数即可达到这个目的:
In [96]: df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=['a', 'b', 'c'],
....: columns=['one', 'two'])
In [97]: df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=['a', 'c'],
....: columns=['three', 'four'])
In [98]: df1
Out[98]:
one two
a 0 1
b 2 3
c 4 5
In [99]: df2
Out[99]:
three four
a 5 6
c 7 8
In [100]: pd.concat([df1, df2], axis=1, keys=['level1', 'level2'])
Out[100]:
level1 level2
one two three four
a 0 1 5.0 6.0
b 2 3 NaN NaN
c 4 5 7.0 8.0
最后一个关于DataFrame的问题是,如果DataFrame的行索引不包含任何相关数据,即行索引作用不大,在这种情况下,传入ignore_index=True即可:
In [105]: df1
Out[105]:
a b c d
0 1.246435 1.007189 -1.296221 0.274992
1 0.228913 1.352917 0.886429 -2.001637
2 -0.371843 1.669025 -0.438570 -0.539741
In [106]: df2
Out[106]:
b d a
0 0.476985 3.248944 -1.021228
1 -0.577087 0.124121 0.302614
In [107]: pd.concat([df1, df2], ignore_index=True)
Out[107]:
a b c d
0 1.246435 1.007189 -1.296221 0.274992
1 0.228913 1.352917 0.886429 -2.001637
2 -0.371843 1.669025 -0.438570 -0.539741
3 -1.021228 0.476985 NaN 3.248944
4 0.302614 -0.577087 NaN 0.124121
In [108]: pd.concat([df1, df2])
Out[108]:
a b c d
0 1.246435 1.007189 -1.296221 0.274992
1 0.228913 1.352917 0.886429 -2.001637
2 -0.371843 1.669025 -0.438570 -0.539741
0 -1.021228 0.476985 NaN 3.248944
1 0.302614 -0.577087 NaN 0.124121
8.合并重叠数据(combine_first)
combine_first可以看作用传递对象中的数据为调用对象的缺失数据“打补丁”,举例就是下面df1的缺失值在df2看看有没有,如果有就用来替换掉df1的缺失值。
In [117]: df1
Out[117]:
a b c
0 1.0 NaN 2
1 NaN 2.0 6
2 5.0 NaN 10
3 NaN 6.0 14
In [118]: df2
Out[118]:
a b
0 5.0 NaN
1 4.0 3.0
2 NaN 4.0
3 3.0 6.0
4 7.0 8.0
In [119]: df1.combine_first(df2)
Out[119]:
a b c
0 1.0 NaN 2.0
1 4.0 2.0 6.0
2 5.0 4.0 10.0
3 3.0 6.0 14.0
4 7.0 8.0 NaN
9. 将“长格式”旋转为“宽格式”(pivot)
pivot()
是一种将 长格式数据 转换为 宽格式数据 的方法,常用于将一个列的值转换为多个列。它允许你将 DataFrame 中的某个列的值,按另外两列进行分组,创建一个以这些列的组合为列标签的宽格式表。
DataFrame.pivot(index=None, columns=None, values=None)
index
:指定新表的行索引。columns
:指定新表的列标签,通常是你想要变成列的那一列。values
:指定要填充的值,可以选择填充的列。如果没有指定,它会选择 DataFrame 中的所有列。
import pandas as pd
df = pd.DataFrame({
'date': ['2021-01-01', '2021-01-01', '2021-01-02', '2021-01-02'],
'city': ['A', 'B', 'A', 'B'],
'temperature': [30, 35, 32, 33]
})
print("Original DataFrame:")
print(df)
Original DataFrame:
date city temperature
0 2021-01-01 A 30
1 2021-01-01 B 35
2 2021-01-02 A 32
3 2021-01-02 B 33
使用 pivot()
操作将上面的DataFrame转化成透视表:
pivot_df = df.pivot(index='date', columns='city', values='temperature')
print("\nPivoted DataFrame:")
print(pivot_df)
Pivoted DataFrame:
city A B
date
2021-01-01 30 35
2021-01-02 32 33
9.将“宽格式”旋转为“长格式”(melt)
melt()
函数是用于将 宽格式数据 转换为 长格式数据 的工具,它可以将多列的数据“拉平”为一列。通常我们在进行数据分析时,melt()
会被用来将不同列的数据整理成一列,并保留其他信息列作为索引。
DataFrame.melt(id_vars=None, value_vars=None, var_name=None, value_name='value')
id_vars
:保留的列,通常是作为唯一标识符的列(例如时间、ID 等),可以为多列,把相应列名的列表传给id_vars就行
。value_vars
:需要“拉平”的列。如果不指定,默认为除id_vars
以外的所有列。var_name
:新列的名称,用来保存原始列名。value_name
:新列的名称,用来保存原始列的值。
import pandas as pd
df = pd.DataFrame({
'Name': ['Alice', 'Bob', 'Charlie'],
'Jan': [100, 200, 300],
'Feb': [150, 250, 350],
'Mar': [200, 300, 400]
})
print("Original DataFrame:")
print(df)
Original DataFrame:
Name Jan Feb Mar
0 Alice 100 150 200
1 Bob 200 250 300
2 Charlie 300 350 400
使用 melt()
进行转换:
我们想要将月份(Jan
, Feb
, Mar
)转换为一列,保留 Name
作为标识符列:
melted_df = df.melt(id_vars='Name', var_name='Month', value_name='Sales')
print("\nMelted DataFrame:")
print(melted_df)
可以看到,value_vars
没有指定,默认为除 id_vars
以外的所有列:
Melted DataFrame:
Name Month Sales
0 Alice Jan 100
1 Bob Jan 200
2 Charlie Jan 300
3 Alice Feb 150
4 Bob Feb 250
5 Charlie Feb 350
6 Alice Mar 200
7 Bob Mar 300
8 Charlie Mar 400
值得一提的是,由pivot得到的所谓“宽的”透视表,再用melt把它转回“长的”,中间要用到reset_index(第4点介绍过)来处理下,主要目的是:
- 将索引列变回普通列,从而使数据结构变得更易于处理(尤其是在转换为长格式数据时)。
- 便于后续处理,如果你之后要使用
melt()
等方法进行数据转换,通常会希望数据的 所有列 都处于同一水平,而不是将某些列当作索引。