欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

数据清理、合并、重塑、转换

程序员文章站 2024-02-28 19:29:52
...

1. 合并数据集
pandas.merge:
根据一个或多个键将不同DataFrame的行连接起来,它实现的是数据库的连接操作。具体如下:

  • 多对一的合并:
df1
Out[4]: 
   data1 key
0      0   b
1      1   b
2      2   a
3      3   c
4      4   a
5      5   a
6      6   b

df2
Out[5]: 
   data2 key
0      0   a
1      1   b
2      2   d

pd.merge(df1,df2)
Out[6]: 
   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

在上述代码中,df1中的数据有多个被标记为a和b的行,而df2中key列的每个值仅对应一行,因此为多对一的合并。还可以显示的指定用哪个列进行连接,如on=’key’,如果进行连接的两个DataFrame没有相同的列名,则可以这样显示指定:

pd.merge(df1,df2,left_on='lkey',right_on='rkey')

我们观察结果集可以发现,c和d以及与子相关的数据都不见了,所以merge默认情况下做的是“inner”连接(即结果中的键是交集),根据已知SQL知识,自然还有其他方式,如left,right和outer(并集)。

  • 多对多连接
    多对多产生的是行的笛卡尔积,如下所示,左边的DataFrame有3个’b’行,右边有两个,那么最终结果中就有6个’b’行:
df1
Out[10]: 
   data1 key
0      0   b
1      1   b
2      2   a
3      3   c
4      4   a
5      5   b

df2
Out[11]: 
   data2 key
0      0   a
1      1   b
2      2   a
3      3   b
4      4   d

pd.merge(df1,df2,on='key',how='left')
Out[12]: 
    data1 key  data2
0       0   b    1.0
1       0   b    3.0
2       1   b    1.0
3       1   b    3.0
4       2   a    0.0
5       2   a    2.0
6       3   c    NaN
7       4   a    0.0
8       4   a    2.0
9       5   b    1.0
10      5   b    3.0
  • 还可以通过多个键进行连接,只需传入一个由列名组成的列表即可,可以做如下理解:多个键形成一系列元组,并将其当做单个键连接(事实并非如此)。
left
Out[19]: 
  key1 key2  lval
0  foo  one     1
1  foo  two     2
2  bar  one     3

right
Out[20]: 
  key1 key2  lval
0  foo  one     4
1  foo  one     5
2  bar  one     6
3  bar  two     7

pd.merge(left,right,on=['key1','key2'],how='outer')
Out[21]: 
  key1 key2  lval_x  lval_y
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选项:用于指定附加到左右两个DataFrame对象的重叠列名上的字符串。
pd.merge(left,right,on='key1')
Out[22]: 
  key1 key2_x  lval_x key2_y  lval_y
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

pd.merge(left,right,on='key1',suffixes=('_left','_right'))
Out[23]: 
  key1 key2_left  lval_left key2_right  lval_right
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
  • 轴向连接
    1. 首先来看numpy中用于合并原始Numpy数组的concatenation函数:
arr
Out[30]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

np.concatenate([arr,arr])
Out[31]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

np.concatenate([arr,arr],axis=1)
Out[32]: 
array([[ 0,  1,  2,  3,  0,  1,  2,  3],
       [ 4,  5,  6,  7,  4,  5,  6,  7],
       [ 8,  9, 10, 11,  8,  9, 10, 11]])
  • 在pandas中为concat函数
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])
Out[36]: 
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: int64

pd.concat([s1,s2,s3],axis=1)
Out[37]: 
     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是在axis=0上工作的,最终产生一个新的Series,如果传入axis=1,则结果就会变成一个DataFrame。

  • 合并重叠数据
    首先使用numpy的where函数
a
Out[41]: 
f    NaN
e    2.5
d    NaN
c    3.5
b    4.5
a    NaN
dtype: float64

b
Out[42]: 
f    0.0
e    1.0
d    2.0
c    3.0
b    4.0
a    5.0
dtype: float64

np.where(pd.isnull(a),b,a)
Out[43]: array([0. , 2.5, 2. , 3.5, 4.5, 5. ])

b[-1]=np.nan

np.where(pd.isnull(a),b,a)
Out[45]: array([0. , 2.5, 2. , 3.5, 4.5, nan])

Series的comnine_first方法有同样的功能,并且可以数据对齐

b.combine_first(a)
Out[46]: 
f    0.0
e    1.0
d    2.0
c    3.0
b    4.0
a    NaN
dtype: float64

同样对于DataFrame,这个函数也会做同样的事情,因此你可以将此看做,用参数对象中的数据为调用者对象的缺失数据“打补丁”
1. 数据转换

  • 移除重复值
    DataFrame的duplicated方法会返回一个布尔型的Series,表示各行是否是重复行
data
Out[48]: 
    k1  k2
0  one   1
1  one   1
2  one   2
3  two   3
4  two   3
5  two   4
6  two   4

data.duplicated()
Out[49]: 
0    False
1     True
2    False
3    False
4     True
5    False
6     True
dtype: bool

drop_duplicates方法,返回一个移除了重复行的DataFrame

data.drop_duplicates()
Out[50]: 
    k1  k2
0  one   1
2  one   2
3  two   3
5  two   4

这两个方法默认保留的是第一个出现的值组合。传入take_last=True则保留最后一个。

  • 利用函数或映射进行数据转换(map)
    我们来看下面有关肉类的数据
data
Out[52]: 
          food  ounces
0        bacon     4.0
1  pulled pork     3.0
2        bacon    12.0
3     Pastrami     6.0
4  corned beef     7.5
5        Bacon     8.0
6     pastrami     3.0
7    honey ham     5.0
8     nova lox     6.0

假设想要添加一列表示该肉类食物的来源,具体映射为:


meat_to_animal
Out[54]: 
{'bacon': 'pig',
 'corned beef': 'cow',
 'honey ham': 'pig',
 'nova lox': 'salmon',
 'pastrami': 'cow',
 'pulled pock': 'pig'}

为了保持一致,先进行大小写转换,然后进行关联(添加列):

data['animal']=data['food'].map(str.lower).map(meat_to_animal)

data
Out[56]: 
          food  ounces  animal
0        bacon     4.0     pig
1  pulled pork     3.0     NaN
2        bacon    12.0     pig
3     Pastrami     6.0     cow
4  corned beef     7.5     cow
5        Bacon     8.0     pig
6     pastrami     3.0     cow
7    honey ham     5.0     pig
8     nova lox     6.0  salmon
  • 离散化和面元划分
    为了便于分析,常常将连续数据离散化为面元。
    假设有一组人员数据,我们希望把它划分为不同的年龄组。
ages
Out[61]: [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]

bins
Out[62]: [18, 25, 35, 60, 100]

cats=pd.cut(ages,bins)

cats
Out[64]: 
[(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的cut函数,它返回的是一个特殊的Categorical对象,它含有一个表示不同分类名称的categories(之前为levels)数组以及一个为年龄数据进行标号的codes(之前是labels)属性:

cats.codes
Out[70]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)

cats.categories
Out[71]: 
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
              closed='right',
              dtype='interval[int64]')

pd.value_counts(cats)
Out[72]: 
(18, 25]     5
(35, 60]     3
(25, 35]     3
(60, 100]    1
dtype: int64

还可以设置属性labels设置为一个列表或元组即可:

group_names=['Youth','YouthAdult','MiddleAged','Senior']

pd.cut(ages,bins,labels=group_names)
Out[74]: 
[Youth, Youth, Youth, YouthAdult, Youth, ..., YouthAdult, Senior, MiddleAged, MiddleAged, YouthAdult]
Length: 12
Categories (4, object): [Youth < YouthAdult < MiddleAged < Senior]

qcut可以根据样板分位数对数据进行面元划分,得到大小基本相等的面元。

data=np.random.randn(1000)  # 正态分布

cats=pd.qcut(data,4)  #按四分位数进行分割

cats
Out[78]: 
[(-2.893, -0.64], (0.0127, 0.739], (0.739, 3.357], (0.0127, 0.739], (-0.64, 0.0127], ..., (-0.64, 0.0127], (0.0127, 0.739], (-0.64, 0.0127], (0.0127, 0.739], (0.739, 3.357]]
Length: 1000
Categories (4, interval[float64]): [(-2.893, -0.64] < (-0.64, 0.0127] < (0.0127, 0.739] < (0.739, 3.357]]

pd.value_counts(cats)
Out[79]: 
(0.739, 3.357]     250
(0.0127, 0.739]    250
(-0.64, 0.0127]    250
(-2.893, -0.64]    250
dtype: int64

跟cut一样,也可以设置自定义的分位数:

pd.qcut(data,[0,0.1,0.5,0.9,1.])
Out[80]: 
[(-2.893, -1.288], (0.0127, 1.316], (0.0127, 1.316], (0.0127, 1.316], (-1.288, 0.0127], ..., (-1.288, 0.0127], (0.0127, 1.316], (-1.288, 0.0127], (0.0127, 1.316], (0.0127, 1.316]]
Length: 1000
Categories (4, interval[float64]): [(-2.893, -1.288] < (-1.288, 0.0127] < (0.0127, 1.316] < (1.316, 3.357]]