Pandas

作者: Alex_杨策 | 来源:发表于2018-09-13 00:19 被阅读0次

    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数据集的创建;
    • 数据集的数据项选择方式;
    • 数据集的自动对齐规则;
    • 数据集的合并方式;
    • 缺失值的处理;
    • 时间序列的处理;

    相关文章

      网友评论

        本文标题:Pandas

        本文链接:https://www.haomeiwen.com/subject/doamgftx.html