利用Python进行数据分析:数据规整(基于DataFrame)

利用Python进行数据分析:数据规整

在许多应用中,数据可能分散在许多文件或数据库中,存储的形式也不利于分析。本部分关注可以聚合、合并、重塑数据的方法。

# 导入包
import pandas as pd
import numpy as np

合并数据集

pandas对象中的数据可以通过一些方式进行合并:

  • pandas.merge可根据一个或多个键将不同DataFrame中的行连接起来。其实现的就是数据库的join操作。
  • pandas.concat可以沿着一条轴将多个对象堆叠到一起。
  • 实例方法combine_first可以将重复数据拼接在一起,用一个对象中的值填充另一个对象中的缺失值。

数据库风格的DataFrame合并

df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
                    'data1': range(7)})
df2 = pd.DataFrame({'key': ['a', 'b', 'd'],
                    'data2': range(3)})
df1
keydata1
0b0
1b1
2a2
3c3
4a4
5a5
6b6
df2
keydata2
0a0
1b1
2d2

通过on=指定用哪个列进行连接:

pd.merge(df1, df2, on='key')
keydata1data2
0b01
1b11
2b61
3a20
4a40
5a50

如果两个对象的列名不同,可以分别指定:

df1 = pd.DataFrame({'key1': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
                    'data1': range(7)})

df2 = pd.DataFrame({'key2': ['a', 'b', 'd'],
                    'data2': range(3)})
pd.merge(df1, df2, left_on='key1', right_on = 'key2')
key1data1key2data2
0b0b1
1b1b1
2b6b1
3a2a0
4a4a0
5a5a0

默认情况下,merge做的是“内连接”;结果中的键是交集。其他方式还有"left"、“right"以及"outer”,通过`how='字段进行指定:

pd.merge(df1, df2, left_on='key1', right_on = 'key2',how = 'right')
key1data1key2data2
0a2.0a0
1a4.0a0
2a5.0a0
3b0.0b1
4b1.0b1
5b6.0b1
6NaNNaNd2

以下列出可选的四种连接方式:

选项说明
inner使用两个表都有的键
left使用左表中所有的键
right使用右表中所有的键
outer使用两个表中所有的键

要根据多个键进行合并,传入一个由列名组成的列表即可:

left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],
                     'key2': ['one', 'two', 'one'],
                     'lval': [1, 2, 3]})
 right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
                       'key2': ['one', 'one', 'one', 'two'],
                       'rval': [4, 5, 6, 7]})
pd.merge(left, right, on=['key1','key2'], how = 'outer')
key1key2lvalrval
0fooone1.04.0
1fooone1.05.0
2footwo2.0NaN
3barone3.06.0
4bartwoNaN7.0

待合并的对象若存在重复列名,可以通过suffixes选项指定附加到重复列名上的字符串:

pd.merge(left, right, on='key1',suffixes=('_left','_right'))
key1key2_leftlvalkey2_rightrval
0fooone1one4
1fooone1one5
2footwo2one4
3footwo2one5
4barone3one6
5barone3two7

以下为merge函数的参数:

在这里插入图片描述
在这里插入图片描述

索引上的合并

上面的pd.merge方法通过right_indexleft_index参数可以用于指定将右侧或左侧的行索引用作连接键,这里不做示例。此部分介绍一个更便捷的join方法,它能更方便地实现按索引合并:

left = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
                     index=['a', 'c', 'e'],
                     columns=['Ohio', 'Nevada'])
right = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
                     index=['b', 'c', 'd', 'e'],
                     columns=['Missouri', 'Alabama'])
left
OhioNevada
a1.02.0
c3.04.0
e5.06.0
right
MissouriAlabama
b7.08.0
c9.010.0
d11.012.0
e13.014.0

因为一些历史版本的遗留原因,DataFrame的join方法默认使用的是左连接,保留左边表的行索引:

left.join(right)
OhioNevadaMissouriAlabama
a1.02.0NaNNaN
c3.04.09.010.0
e5.06.013.014.0

对于简单的索引合并,你还可以向join传入一组DataFrame:

another = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [16., 17.]],
                       index=['a', 'c', 'e', 'f'],
                       columns=['New York','Oregon'])
another
New YorkOregon
a7.08.0
c9.010.0
e11.012.0
f16.017.0

join支持{‘left’, ‘right’, ‘outer’, ‘inner’}四种连接方式:

left.join([right,another], how='outer')
OhioNevadaMissouriAlabamaNew YorkOregon
a1.02.0NaNNaN7.08.0
c3.04.09.010.09.010.0
e5.06.013.014.011.012.0
bNaNNaN7.08.0NaNNaN
dNaNNaN11.012.0NaNNaN
fNaNNaNNaNNaN16.017.0

轴向连接

假设有三个没有重叠索引的Series,pd.concat可以将值和索引堆叠在一起,默认在axis=0上工作:

s1 = pd.Series([0, 1], index=['a', 'b'])
s2 = pd.Series([2, 3, 4], index=['c', 'd', 'e'])
s3 = pd.Series([5, 6], index=['f', 'g'])
pd.concat([s1,s2,s3])
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: int64

传入axis=1,在列的方向上进行连接,会变成一个DataFrame:

pd.concat([s1,s2,s3], axis=1)
012
a0.0NaNNaN
b1.0NaNNaN
cNaN2.0NaN
dNaN3.0NaN
eNaN4.0NaN
fNaNNaN5.0
gNaNNaN6.0

concat之后不知道堆叠的数据代表什么,可以通过keys参数设定连接的片段在结果中的索引:

#沿着axis=0对Series进行合并,生成层次化索引
pd.concat([s1,s2,s3], keys = ['s1','s2','s3'])
s1  a    0
    b    1
s2  c    2
    d    3
    e    4
s3  f    5
    g    6
dtype: int64
#如果沿着axis=1对Series进行合并,则keys就会成为DataFrame的列头:
pd.concat([s1,s2,s3], axis=1, keys = ['s1','s2','s3'])
s1s2s3
a0.0NaNNaN
b1.0NaNNaN
cNaN2.0NaN
dNaN3.0NaN
eNaN4.0NaN
fNaNNaN5.0
gNaNNaN6.0

如果索引存在重叠,可以通过join=参数控制连接方式为innerouter(默认为’outer’):

s4 = pd.concat([s1, s3])
s1
a    0
b    1
dtype: int64
s4
a    0
b    1
f    5
g    6
dtype: int64
pd.concat([s1,s4], axis =1)
01
a0.00
b1.01
fNaN5
gNaN6
pd.concat([s1,s4], axis =1, join='inner')
01
a00
b11

上述Series同样的逻辑也适用于DataFrame对象。需说明的是,如果DataFrame的行索引不包含任何有意义的数据,可以通过传入ignore_index=True声明不保留连接轴上的索引,产生一组新索引range(total_length):

df1 = pd.DataFrame(np.random.randn(3, 4), columns=['a', 'b', 'c', 'd'])
df2 = pd.DataFrame(np.random.randn(2, 3), columns=['b', 'd', 'a'])
df1
abcd
00.095987-1.4296830.2131540.209158
10.4050742.0602630.154940-1.547939
2-0.2837980.185476-0.8331570.204095
df2
bda
0-0.743573-0.081083-0.832971
10.432616-0.393941-1.465352
pd.concat([df1,df2], ignore_index = True)
abcd
00.095987-1.4296830.2131540.209158
10.4050742.0602630.154940-1.547939
2-0.2837980.185476-0.8331570.204095
3-0.832971-0.743573NaN-0.081083
4-1.4653520.432616NaN-0.393941

合并重叠数据

比如说,你可能有索引全部或部分重叠的两个数据集。你希望用一个数据集去填补另一个数据集中相应字段的缺失值:

s1 = pd.Series([np.nan, 2.5, np.nan, 3.5, 4.5, np.nan],
              index=['f', 'e', 'd', 'c', 'b', 'a'])
s2 = pd.Series(np.arange(len(a), dtype=np.float64),
              index=['f', 'e', 'd', 'c', 'b', 'a'])
s2[-1] = np.nan
s1
f    NaN
e    2.5
d    NaN
c    3.5
b    4.5
a    NaN
dtype: float64
s2
f    0.0
e    1.0
d    2.0
c    3.0
b    4.0
a    NaN
dtype: float64

方法一:np.where,表示一种等价于面向数组的if-else

# 条件表达式成立即s1为空值的地方,取s2中值,否则保留s1中值
np.where(pd.isnull(s1), s2, s1)
array([0. , 2.5, 2. , 3.5, 4.5, nan])

方法二:fillna

s1.fillna(s2)
f    0.0
e    2.5
d    2.0
c    3.5
b    4.5
a    NaN
dtype: float64

方法三:combine_first

s1.combine_first(s2)
f    0.0
e    2.5
d    2.0
c    3.5
b    4.5
a    NaN
dtype: float64

以上方法对DataFrame同样适用,用传递对象中的数据为调用对象的缺失数据“打补丁”:

df1 = pd.DataFrame({'a': [1., np.nan, 5., np.nan],
                     'b': [np.nan, 2., np.nan, 6.],
                     'c': range(2, 18, 4)})
df2 = pd.DataFrame({'a': [5., 4., np.nan, 3., 7.],
                    'b': [np.nan, 3., 4., 6., 8.]})
df1
abc
01.0NaN2
1NaN2.06
25.0NaN10
3NaN6.014
df2
ab
05.0NaN
14.03.0
2NaN4.0
33.06.0
47.08.0
df1.combine_first(df2)
abc
01.0NaN2.0
14.02.06.0
25.04.010.0
33.06.014.0
47.08.0NaN

层次化索引

层次化索引(hierarchical indexing)是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。

对于一个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']])
frame
OhioColorado
GreenRedGreen
a1012
2345
b1678
291011

各层都可以有名字:

frame.index.names = ['key1','key2']
frame.columns.names = ['state','color']
frame
stateOhioColorado
colorGreenRedGreen
key1key2
a1012
2345
b1678
291011

重排与分级排序

swaplevel接受两个级别编号或名称,并返回一个互换了级别的新对象(但数据不会发生变化):

frame.swaplevel(0,1)
stateOhioColorado
colorGreenRedGreen
key2key1
1a012
2a345
1b678
2b91011
frame.swaplevel('key1','key2')
stateOhioColorado
colorGreenRedGreen
key2key1
1a012
2a345
1b678
2b91011

sort_index则根据单个级别中的值对数据进行排序。交换级别时,常会用到该命令。

frame.swaplevel('key1','key2').sort_index(level=0)
stateOhioColorado
colorGreenRedGreen
key2key1
1a012
b678
2a345
b91011

使用DataFrame的列进行索引

在一些数据分析的场景中,可能想要将DataFrame的一个或多个列当做行索引来用,或者可能希望将行索引变成DataFrame的列。

frame = pd.DataFrame({'a': range(7), 'b': range(7, 0, -1),
                      'c': ['one', 'one', 'one', 'two', 'two', 'two', 'two'],
                      'd': [0, 1, 2, 0, 1, 2, 3]})
frame
abcd
007one0
116one1
225one2
334two0
443two1
552two2
661two3

DataFrame的set_index函数会将其一个或多个列转换为行索引,并创建一个新的DataFrame:

frame2 = frame.set_index(['c','d'])
frame2
ab
cd
one007
116
225
two034
143
252
361

reset_index的功能跟set_index刚好相反,层次化索引的级别会被转移到列里面:

frame2.reset_index()
cdab
0one007
1one116
2one225
3two034
4two143
5two252
6two361

数据重塑

层次化索引为DataFrame数据的重排任务提供了一种具有良好一致性的方式。主要功能有二:

  • stack:将数据的列“旋转”为行。
  • unstack:将数据的行“旋转”为列。

比如,以下数据为a、b、c、d四名学生,1、2、3三门课程的考试成绩:

data = pd.DataFrame(list(zip(['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'],
                             [1, 2, 3, 1, 3, 1, 2, 2, 3],
                             np.random.randn(9))),
                    columns = ['student','course','score'])
data
studentcoursescore
0a10.469069
1a20.568829
2a31.293216
3b1-0.799458
4b32.323059
5c10.419970
6c20.576399
7d20.459112
8d3-0.654535

层次化索引在数据重塑和基于分组的操作(如透视表生成)中扮演着重要的角色,例如:

首先,将"student"、"course"设置为index, 形成层次化索引:

data = data.set_index(['student','course'])
data
score
studentcourse
a10.469069
20.568829
31.293216
b1-0.799458
32.323059
c10.419970
20.576399
d20.459112
3-0.654535

其次,通过unstack命令对数据进行重塑,得到学生和课程的透视表,这样便于后续对成绩之间进行一些统计运算:

result = data.unstack()
result
score
course123
student
a0.4690690.5688291.293216
b-0.799458NaN2.323059
c0.4199700.576399NaN
dNaN0.459112-0.654535

stack为unstack的逆运算:

result.stack()
score
studentcourse
a10.469069
20.568829
31.293216
b1-0.799458
32.323059
c10.419970
20.576399
d20.459112
3-0.654535

往期:
利用Python进行数据分析:准备工作
利用Python进行数据分析:缺失数据(基于DataFrame)
利用Python进行数据分析:数据转换(基于DataFrame)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值