Pandas

Pandas建立在NumPy基础上,处理二维数据更加得心应手。

Series和DataFrame

Series和DataFrame是Pandas中的两种核心数据结构,大部分Pandas的功能都围绕着两种这两种数据结构进行。

Series是值得序列,可以理解为一维数组,只有一个列和索引。索引可以定制,当不指定时默认使用整数索引,而且索引可以被命名:

In [1]: import pandas as pd
In [2]: import numpy as np
In [4]: from pandas import Series, DataFrame

In [5]: s1 = Series([1, 2, 3, 4, 5])

In [6]: s1
Out[6]:
0    1
1    2
2    3
3    4
4    5
dtype: int64

In [7]: s2 = Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e' ])

In [8]: s2
Out[8]:
a    1
b    2
c    3
d    4
e    5
dtype: int64

In [9]: s2.index.name = 'index'

In [10]: s2.index
Out[10]: Index(['a', 'b', 'c', 'd', 'e'], dtype='object', name='index')

DataFrame类似于二维数组,有行和列之分,除了像Series一样,多个行有索引之外,每个列上面还可以有标签label,索引和标签本身都可以被命名:

In [14]: df = DataFrame(np.random.randn(4, 4), index=['a', 'b', 'c', 'd'], columns=['A', 'B', 'C', 'D'])

In [15]: df
Out[15]:
          A         B         C         D
a  0.311412  0.063509  1.195284 -0.921551
b  0.906860 -1.183861 -0.002180 -2.067180
c -0.121993  0.494496  0.870654  0.852347
d -0.461892  0.784124  0.069672 -0.813653

In [16]: df.index
Out[16]: Index(['a', 'b', 'c', 'd'], dtype='object')

In [17]: df.columns
Out[17]: Index(['A', 'B', 'C', 'D'], dtype='object')

上面得代码,通过指定索引和标签(columns参数)创建了一个DataFrame实例。可以通过df.index和df.columns分别访问索引和标签。

选择

对于Series数据来说,主要通过其索引来进行选择:

In [18]: s2 = Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])

In [19]: s2
Out[19]:
a    1
b    2
c    3
d    4
e    5
dtype: int64

In [20]: s2[0]
Out[20]: 1

In [21]: s2['a']
Out[21]: 1

In [22]: s2[0:3]
Out[22]:
a    1
b    2
c    3
dtype: int64

In [23]: s2['a':'c']
Out[23]:
a    1
b    2
c    3
dtype: int64

对于指定了索引的Series序列来说,有两种选择元素的方式,一种是整数索引,(说明整数索引一直默认存在),第二种方式是通过指定字符索引进行。整数索引和字符索引分别调用了s2.iloc和s2.loc索引,其中iloc代表整数索引:

In [24]: s2.iloc[0:3]
Out[24]:
a    1
b    2
c    3
dtype: int64

In [25]: s2.loc['a':'c']
Out[25]:
a    1
b    2
c    3
dtype: int64

对于DateFrame数据,由于有行列之分,可以有多种选择数据的方式,可以通过索引和标签来选择,对于标签(列)选择:

In [27]: df.A
Out[27]:
a    0.311412
b    0.906860
c   -0.121993
d   -0.461892
Name: A, dtype: float64
In [29]: df['A']
Out[29]:
a    0.311412
b    0.906860
c   -0.121993
d   -0.461892
Name: A, dtype: float64

对于标签(列),可以通过df.A的属性方式,或者通过df['A']来访问。如果要选择多列,可以通过df.columns来获取所有列标签,然后选择部分标签来选择df的多列数据。

In [30]: df[df.columns[0:2]]
Out[30]:
          A         B
a  0.311412  0.063509
b  0.906860 -1.183861
c -0.121993  0.494496
d -0.461892  0.784124

可以用loc和iloc属性来选择某一行

In [33]: df
Out[33]:
          A         B         C         D
a  0.311412  0.063509  1.195284 -0.921551
b  0.906860 -1.183861 -0.002180 -2.067180
c -0.121993  0.494496  0.870654  0.852347
d -0.461892  0.784124  0.069672 -0.813653

In [34]: df.loc['a']
Out[34]:
A    0.311412
B    0.063509
C    1.195284
D   -0.921551
Name: a, dtype: float64

In [35]: df.loc['a':'b']
Out[35]:
          A         B         C         D
a  0.311412  0.063509  1.195284 -0.921551
b  0.906860 -1.183861 -0.002180 -2.067180

In [36]: df.iloc[0]
Out[36]:
A    0.311412
B    0.063509
C    1.195284
D   -0.921551
Name: a, dtype: float64

In [37]: df.iloc[0:2]
Out[37]:
          A         B         C         D
a  0.311412  0.063509  1.195284 -0.921551
b  0.906860 -1.183861 -0.002180 -2.067180

有了行选择方式就有了更多方法来选择列:

In [38]: df.loc[:,['B', 'C', 'D']]
Out[38]:
          B         C         D
a  0.063509  1.195284 -0.921551
b -1.183861 -0.002180 -2.067180
c  0.494496  0.870654  0.852347
d  0.784124  0.069672 -0.813653

df.loc支持二维选择,可以同时选择行和列,:符号代表选择所有的行。可以同时选择具体的某一行或者某一列:

In [39]: df
Out[39]:
          A         B         C         D
a  0.311412  0.063509  1.195284 -0.921551
b  0.906860 -1.183861 -0.002180 -2.067180
c -0.121993  0.494496  0.870654  0.852347
d -0.461892  0.784124  0.069672 -0.813653

In [40]: df.loc['a', 'A']
Out[40]: 0.31141203738121559

In [41]: df.loc['b':'c', 'B':'C']
Out[41]:
          B         C
b -1.183861 -0.002180
c  0.494496  0.870654

缺失值和数据自动对齐

Pandas中最重要的一个功能是,它可以对不同索引的对象进行算术运算。比如对两个Series数据进行相加时,如果存在不用的索引,则结果时两个索引的并集:

In [42]: s1 = Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])

In [43]: s1
Out[43]:
a    1
b    2
c    3
d    4
dtype: int64

In [44]: s2 = Series([2, 3, 4, 5], index=['b', 'c', 'd', 'e'])

In [45]: s2
Out[45]:
b    2
c    3
d    4
e    5
dtype: int64

In [46]: s1 + s2
Out[46]:
a    NaN
b    4.0
c    6.0
d    8.0
e    NaN
dtype: float64

以上代码中创建了s1和s2两个序列,具有相同的索引['b', 'c', 'd'],所以在相加时,相同索引的值会相加,但不重叠的索引会引入NaN值,也就是缺失值。缺失值会在运算中传播,所以最终结果也是NaN值。
根据相同索引进行自动计算,就是自动对齐功能。同样的规则也适用于DataFrame:

In [49]: df1 = DataFrame(np.arange(9).reshape(3, 3), columns=list('ABC'), index=list('abc'
    ...: ))

In [50]: df2 = DataFrame(np.arange(12).reshape(3, 4), columns=list('ABDE'), index=list('bc
    ...: d'))

In [51]: df1
Out[51]:
   A  B  C
a  0  1  2
b  3  4  5
c  6  7  8

In [52]: df2
Out[52]:
   A  B   D   E
b  0  1   2   3
c  4  5   6   7
d  8  9  10  11

In [53]: df1 + df2
Out[53]:
      A     B   C   D   E
a   NaN   NaN NaN NaN NaN
b   3.0   5.0 NaN NaN NaN
c  10.0  12.0 NaN NaN NaN
d   NaN   NaN NaN NaN NaN

在计算时可以指定使用的值来填充NaN值,然后带入计算过程:

In [54]: df1.add(df2, fill_value=0)
Out[54]:
      A     B    C     D     E
a   0.0   1.0  2.0   NaN   NaN
b   3.0   5.0  5.0   2.0   3.0
c  10.0  12.0  8.0   6.0   7.0
d   8.0   9.0  NaN  10.0  11.0

指定了使用0来填充NaN值,然后带入计算过程,注意这里填充的不是最终的计算结果。可以看到依然有元素为NoN值,是因为这个位置的元素在df1和df2中都未定义。

常用运算

Series和DataFrame的常用运算方式和NumPy中的差不多。在Pandas中还有一种比较常见的操作是将函数应用到每行或者每列上面,DataFrame的apply方法可以实现此功能,比如想统计每行和每列的极差(最大值和最小值之差):

In [55]: df1 = DataFrame(np.arange(9).reshape(3, 3), columns=list('ABC'), index=list('abc'
    ...: ))

In [56]: df1
Out[56]:
   A  B  C
a  0  1  2
b  3  4  5
c  6  7  8

In [57]: f = lambda x: x.max() - x.min()

In [58]: df1.apply(f)
Out[58]:
A    6
B    6
C    6
dtype: int64

In [59]: df1.apply(f, axis=1)
Out[59]:
a    2
b    2
c    2
dtype: int64

以上代码中,定义了f匿名函数,通过df.apply(f)应用f,统计出每列的极差,接着通过传入 axis=1 参数,统计出每行的极差。
如果想把函数应用到每一个元素上,DateFrame数据可以使用df.applymap方法,Series可以使用s.map方法:

In [60]: df1.applymap(lambda x: x+1)
Out[60]:
   A  B  C
a  1  2  3
b  4  5  6
c  7  8  9

以上代码中,使所有元素都自增加1,其结果和 df1+1的运算结果相同。

常用统计

类似于NumPy, Series和DataFrame也有各种统计方法,比如求平均值,方差,和等方法,同时通过describe方法可以得到当前数据的一些常用的统计信息:

In [61]: df1 = DataFrame(np.arange(9).reshape(3,3), columns=list('ABC'), index=list('abc')
    ...: )

In [62]: df1
Out[62]:
   A  B  C
a  0  1  2
b  3  4  5
c  6  7  8

In [63]: df1.sum()
Out[63]:
A     9
B    12
C    15
dtype: int64

In [64]: df1.mean()
Out[64]:
A    3.0
B    4.0
C    5.0
dtype: float64

In [65]: df1.sum(axis=1)
Out[65]:
a     3
b    12
c    21
dtype: int64

In [66]: df1.describe()
Out[66]:
         A    B    C
count  3.0  3.0  3.0
mean   3.0  4.0  5.0
std    3.0  3.0  3.0
min    0.0  1.0  2.0
25%    1.5  2.5  3.5
50%    3.0  4.0  5.0
75%    4.5  5.5  6.5
max    6.0  7.0  8.0

对于这些统计函数,我们可以指定统计的纬度,和NumPy多维数组的统计函数十分相似。默认按列统计,也可以通过参数axis=1指定按行统计。
describe方法显示了一些常用的统计信息:

- 'conut' 元素值得数量:
- 'mean' 平均值:
- 'std' 标准差:
- 'min' 最小值:
- '25%' 下四分位数:
- '50%' 中位数:
- '75%' 上四分位数:
- 'max' 最大值:

数据合并和分组

有时候需要合并两个DataFrame数据,合并数据得方式有两种,一种简单得进行拼接,另一种是根据列名类像数据库查询一样进行合并。这两种方法分别通过pandas.concat和pandas.merge方法实现:

In [67]: df1 = DataFrame(np.random.randn(3, 3))
In [69]: df2 = DataFrame(np.random.randn(3, 3), index=[5, 6, 7])

In [70]: df1
Out[70]:
          0         1         2
0 -0.975353  0.516803 -0.784033
1  0.243648 -0.671816 -1.232789
2  0.433984  1.175839 -2.421708

In [71]: df2
Out[71]:
          0         1         2
5 -1.261180  0.043069  0.301943
6  0.747142 -0.113258 -0.700924
7 -0.687073  0.834874  0.790432

In [72]: pd.concat([df1, df2])
Out[72]:
          0         1         2
0 -0.975353  0.516803 -0.784033
1  0.243648 -0.671816 -1.232789
2  0.433984  1.175839 -2.421708
5 -1.261180  0.043069  0.301943
6  0.747142 -0.113258 -0.700924
7 -0.687073  0.834874  0.790432

有的时候,有多个数据集,这些数据集有相同的列,就可以按照这个列进行合并操作,类似于数据库中的join操作:

In [73]: df1 = DataFrame({'user_id': [5348, 13], 'course': [12, 45], 'minutes': [9, 36]})

In [74]: df2 = DataFrame({'course': [12, 45], 'name': ['Linux基础入门', '数据分析']})

In [75]: df1
Out[75]:
   course  minutes  user_id
0      12        9     5348
1      45       36       13

In [76]: df2
Out[76]:
   course       name
0      12  Linux基础入门
1      45       数据分析

In [77]: pd.merge(df1, df2)
Out[77]:
   course  minutes  user_id       name
0      12        9     5348  Linux基础入门
1      45       36       13       数据分析

可以看到当通过字段创建DataFrame数据集时,键变成了列名。df1和df2有共同的列course,当进行merge操作的时候Pandas会自动安装这列进行合并。

在Pandas中,也支持类似数据库查询语句GROUP BY的功能,也就是按照某列进行分组,然后在分组上进行一些计算操作,有如下数据集,计算其中user_id为5348的用户的学习时间:

In [79]: df = DataFrame({'user_id': [5348, 13, 5348], 'course': [12, 45, 23], 'minutes': [
    ...: 9, 36, 45]})

In [80]: df
Out[80]:
   course  minutes  user_id
0      12        9     5348
1      45       36       13
2      23       45     5348

一种办法时筛选出所有user_id为5348的行,然后进行求和统计:

In [81]: df[df['user_id'] == 5348]
Out[81]:
   course  minutes  user_id
0      12        9     5348
2      23       45     5348

In [82]: df[df['user_id'] == 5348]['minutes']
Out[82]:
0     9
2    45
Name: minutes, dtype: int64

In [83]: df[df['user_id'] == 5348]['minutes'].sum()
Out[83]: 54

也可以使用类似于数据库的 GROUP BY功能进行计算:

In [84]: df[['user_id', 'minutes']]
Out[84]:
   user_id  minutes
0     5348        9
1       13       36
2     5348       45

In [85]: df[['user_id', 'minutes']].groupby('user_id').sum()
Out[85]:
         minutes
user_id
13            36
5348          54

相对于第一种方式,通过groupby方法在user_id上进行分组并求和,相对于前一种方式,分组求和更加灵活一些。

时间序列处理

要分析的数据中,很多数据都带有时间戳,用Pandas根据时间戳进行计算很方便。
创建一个简单的时间序列:

from datetime import datetime
In [88]: dates = [datetime(2018, 1, 1), datetime(2018, 1, 2), datetime(2018, 1, 3), dateti
    ...: me(2018, 1, 4)]

In [89]: ts = Series(np.random.randn(4), index=dates)

In [90]: ts
Out[90]:
2018-01-01    0.885089
2018-01-02    0.114941
2018-01-03   -0.831710
2018-01-04    0.382641
dtype: float64

In [91]: ts.index
Out[91]: DatetimeIndex(['2018-01-01', '2018-01-02', '2018-01-03', '2018-01-04'], dtype='datetime64[ns]', freq=None)

In [92]: ts.index[0]
Out[92]: Timestamp('2018-01-01 00:00:00')

通过datetime时间类型生成了一堆时间,然后基于此创建了一个时间序列ts,ts的索引类型为DatetimeIndex类型。生成ts以后我们就有了多种发放选择元素了,只需要传入一个能被Pandas识别的日期字符串就可以:

In [92]: ts.index[0]
Out[92]: Timestamp('2018-01-01 00:00:00')

In [93]: ts[ts.index[0]]
Out[93]: 0.8850886421792693

In [94]: ts['2018-01-01']
Out[94]: 0.8850886421792693

In [95]: ts['2018/01/01']
Out[95]: 0.8850886421792693

In [96]: ts['1/1/2018']
Out[96]: 0.8850886421792693

In [97]: ts[datetime(2018, 1, 1)]
Out[97]: 0.8850886421792693

在Pandas中生成日期范围也非常灵活,主要通过pandas.date_range函数完成,该函数主要有以下几个参数:

  • start: 指定了日期范围的起始时间;

  • end: 指定了日期范围的结束时间;

  • periods: 指定了间隔范围,如果只是指定了start和end日期的其中一个,则需要该参数;

  • freq: 指定了日期频率,比如D代表每天,H代表每小时,M代表月,这些频率字符前也可以指定一个整数,代表具体多少天,多少小时,比如5D代表5天。MS代表每月第一天,BM代表每月最后一个工作日,或者时频率组合字符串,1h30min代表1小时30分钟。

In [98]: pd.date_range('2018-1-1', '2019', freq='M')
Out[98]:
DatetimeIndex(['2018-01-31', '2018-02-28', '2018-03-31', '2018-04-30',
               '2018-05-31', '2018-06-30', '2018-07-31', '2018-08-31',
               '2018-09-30', '2018-10-31', '2018-11-30', '2018-12-31'],
              dtype='datetime64[ns]', freq='M')

In [99]: pd.date_range('2018-1-1', '2018-12-1', freq='MS')
Out[99]:
DatetimeIndex(['2018-01-01', '2018-02-01', '2018-03-01', '2018-04-01',
               '2018-05-01', '2018-06-01', '2018-07-01', '2018-08-01',
               '2018-09-01', '2018-10-01', '2018-11-01', '2018-12-01'],
              dtype='datetime64[ns]', freq='MS')

有时候时间序列是按每小时显示统计的,但是我们想将统计频率转换成按天来统计,可以使用时间序列的resample方法,该方法非常强大,不仅仅支持高频率的数据聚合到低频率(降采样),也支持低频率转化到高频率统计(升采样):

In [100]: dates = pd.date_range('2018-1-1', '2018-1-2 23:00:00', freq='H')

In [101]: ts = Series(np.arange(len(dates)), index=dates)

In [102]: ts.size
Out[102]: 48

In [103]: ts.head(5)
Out[103]:
2018-01-01 00:00:00    0
2018-01-01 01:00:00    1
2018-01-01 02:00:00    2
2018-01-01 03:00:00    3
2018-01-01 04:00:00    4
Freq: H, dtype: int32

In [104]: ts.tail(5)
Out[104]:
2018-01-02 19:00:00    43
2018-01-02 20:00:00    44
2018-01-02 21:00:00    45
2018-01-02 22:00:00    46
2018-01-02 23:00:00    47
Freq: H, dtype: int32

以上代码,创建了以每小时为频率的时间序列,该数据集有48个元素,使用ts.head(5)方法显示前5个元素,使用ts.tail(5)显示后5个元素。
把以上数据转换为按天数据统计:

In [107]: ts.resample('D').sum()
Out[107]:
2018-01-01    276
2018-01-02    852
Freq: D, dtype: int32

也可以按天的所有数据的平均数进行统计:

In [108]: ts.resample('D').mean()
Out[108]:
2018-01-01    11.5
2018-01-02    35.5
Freq: D, dtype: float64

当把低频率的数据转换成高频率的数据时,默认情况下Pandas会引入NoN值,因为没有办法从低频率的数据计算出高频率的数据,但可以通过fill_method参数指定插值方式:

In [109]: ts.resample('D').mean().resample('H').mean()
Out[109]:
2018-01-01 00:00:00    11.5
2018-01-01 01:00:00     NaN
2018-01-01 02:00:00     NaN
....
2018-01-01 21:00:00     NaN
2018-01-01 22:00:00     NaN
2018-01-01 23:00:00     NaN
2018-01-02 00:00:00    35.5
Freq: H, dtype: float64

以上代码,我们先将ts转换为按天统计,接着又转换成按小时平均值统计,Pandas引入NaN值,但当使用ffill(用前面的值代替NaN值)就不会又NaN值出现:

In [110]: ts.resample('D').mean().resample('H').ffill()
Out[110]:
2018-01-01 00:00:00    11.5
2018-01-01 01:00:00    11.5
....
2018-01-01 23:00:00    11.5
2018-01-02 00:00:00    35.5
Freq: H, dtype: float64

Pandas总结

Pandas虽然构建于NumPy之上,但提供了更灵活强大的功能:

  • Series 和 DataFrame数据集的创建;
  • 数据集的数据项选择方式;
  • 数据集的自动对齐规则;
  • 数据集的合并方式;
  • 缺失值的处理;
  • 时间序列的处理;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容