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

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

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

文章目录

  • 利用Python进行数据分析:数据规整
    • 合并数据集
      • 数据库风格的DataFrame合并
      • 索引上的合并
      • 轴向连接
      • 合并重叠数据
    • 层次化索引
      • 重排与分级排序
      • 使用DataFrame的列进行索引
    • 数据重塑

# 导入包
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
key data1
0 b 0
1 b 1
2 a 2
3 c 3
4 a 4
5 a 5
6 b 6
df2
key data2
0 a 0
1 b 1
2 d 2

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

pd.merge(df1, df2, on='key')
key data1 data2
0 b 0 1
1 b 1 1
2 b 6 1
3 a 2 0
4 a 4 0
5 a 5 0

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

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')
key1 data1 key2 data2
0 b 0 b 1
1 b 1 b 1
2 b 6 b 1
3 a 2 a 0
4 a 4 a 0
5 a 5 a 0

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

pd.merge(df1, df2, left_on='key1', right_on = 'key2',how = 'right')
key1 data1 key2 data2
0 a 2.0 a 0
1 a 4.0 a 0
2 a 5.0 a 0
3 b 0.0 b 1
4 b 1.0 b 1
5 b 6.0 b 1
6 NaN NaN d 2

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

选项 说明
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')
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

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

pd.merge(left, right, on='key1',suffixes=('_left','_right'))
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

以下为merge函数的参数:

利用Python进行数据分析:数据规整(基于DataFrame)_第1张图片
利用Python进行数据分析:数据规整(基于DataFrame)_第2张图片

索引上的合并

上面的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
Ohio Nevada
a 1.0 2.0
c 3.0 4.0
e 5.0 6.0
right
Missouri Alabama
b 7.0 8.0
c 9.0 10.0
d 11.0 12.0
e 13.0 14.0

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

left.join(right)
Ohio Nevada Missouri Alabama
a 1.0 2.0 NaN NaN
c 3.0 4.0 9.0 10.0
e 5.0 6.0 13.0 14.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 York Oregon
a 7.0 8.0
c 9.0 10.0
e 11.0 12.0
f 16.0 17.0

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

left.join([right,another], how='outer')
Ohio Nevada Missouri Alabama New York Oregon
a 1.0 2.0 NaN NaN 7.0 8.0
c 3.0 4.0 9.0 10.0 9.0 10.0
e 5.0 6.0 13.0 14.0 11.0 12.0
b NaN NaN 7.0 8.0 NaN NaN
d NaN NaN 11.0 12.0 NaN NaN
f NaN NaN NaN NaN 16.0 17.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)
0 1 2
a 0.0 NaN NaN
b 1.0 NaN NaN
c NaN 2.0 NaN
d NaN 3.0 NaN
e NaN 4.0 NaN
f NaN NaN 5.0
g NaN NaN 6.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'])
s1 s2 s3
a 0.0 NaN NaN
b 1.0 NaN NaN
c NaN 2.0 NaN
d NaN 3.0 NaN
e NaN 4.0 NaN
f NaN NaN 5.0
g NaN NaN 6.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)
0 1
a 0.0 0
b 1.0 1
f NaN 5
g NaN 6
pd.concat([s1,s4], axis =1, join='inner')
0 1
a 0 0
b 1 1

上述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
a b c d
0 0.095987 -1.429683 0.213154 0.209158
1 0.405074 2.060263 0.154940 -1.547939
2 -0.283798 0.185476 -0.833157 0.204095
df2
b d a
0 -0.743573 -0.081083 -0.832971
1 0.432616 -0.393941 -1.465352
pd.concat([df1,df2], ignore_index = True)
a b c d
0 0.095987 -1.429683 0.213154 0.209158
1 0.405074 2.060263 0.154940 -1.547939
2 -0.283798 0.185476 -0.833157 0.204095
3 -0.832971 -0.743573 NaN -0.081083
4 -1.465352 0.432616 NaN -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
a b c
0 1.0 NaN 2
1 NaN 2.0 6
2 5.0 NaN 10
3 NaN 6.0 14
df2
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
df1.combine_first(df2)
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

层次化索引

层次化索引(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
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']
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

重排与分级排序

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

frame.swaplevel(0,1)
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
frame.swaplevel('key1','key2')
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则根据单个级别中的值对数据进行排序。交换级别时,常会用到该命令。

frame.swaplevel('key1','key2').sort_index(level=0)
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

使用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
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

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

frame2 = frame.set_index(['c','d'])
frame2
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刚好相反,层次化索引的级别会被转移到列里面:

frame2.reset_index()
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

数据重塑

层次化索引为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
student course score
0 a 1 0.469069
1 a 2 0.568829
2 a 3 1.293216
3 b 1 -0.799458
4 b 3 2.323059
5 c 1 0.419970
6 c 2 0.576399
7 d 2 0.459112
8 d 3 -0.654535

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

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

data = data.set_index(['student','course'])
data
score
student course
a 1 0.469069
2 0.568829
3 1.293216
b 1 -0.799458
3 2.323059
c 1 0.419970
2 0.576399
d 2 0.459112
3 -0.654535

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

result = data.unstack()
result
score
course 1 2 3
student
a 0.469069 0.568829 1.293216
b -0.799458 NaN 2.323059
c 0.419970 0.576399 NaN
d NaN 0.459112 -0.654535

stack为unstack的逆运算:

result.stack()
score
student course
a 1 0.469069
2 0.568829
3 1.293216
b 1 -0.799458
3 2.323059
c 1 0.419970
2 0.576399
d 2 0.459112
3 -0.654535

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

你可能感兴趣的:(数据分析,数据分析,python,数据挖掘)