Python数据分析之Pandas

  • A+
所属分类:python数据分析
摘要这一篇文章是pandas的介绍。最近可能用这个库用的比较多,之后再有什么详细的可以继续更新。这一篇就把pandas的用法简单介绍一下。

Pandas 建立在 NumPy 基础之上,但增加了更加高级实用的功能,比如数据自动对齐功能,时间序列的支持,缺失数据的灵活处理等等。这里我们就会介绍一下Pandas

Pandas介绍

数据分析中,我们更多的针对表格数据进行处理,也就是 NumPy 中的二维数组数据,尽管 NumPy 对于多维数组的支持已经足够强大,但 Pandas 处理这些二维数据时更加得心应手。Pandas 建立在 NumPy 基础之上,但增加了更加高级实用的功能,比如数据自动对齐功能,时间序列的支持,缺失数据的灵活处理等等。

为了方便,下面代码中出现的pd,SeriesDataFrame已经在 IPython终端中通过以下方式导入:

import numpy as np
import pandas as pd
from pandas import Series,DataFrame

Series和DataFrame

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

Series介绍

创建Series数据

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

我们首先生成一个默认的整数索引的Series

s1 = Series([1,2,3,4,5])
>> 
0    1
1    2
2    3
3    4
4    5
dtype: int64

默认索引可以看到是使用整数索引的。接下来我们看一下对索引进行重命名,并且对索引进行命名。

s1 = Series([1,2,3,4,5],index=['a','b','c','d','e'])
s1.index.name = 'index'
>> 
index
a    1
b    2
c    3
d    4
e    5
dtype: int64

选择

当我们生成了Series数据后,我们应该如何选择其中的部分数据呢。对于Series数据,我们主要通过索引来进行选择。

可以看到对于指定了索引的Series序列来说,我们有两种选择元素的方式,一种是以整数索引(这说明整数索引一直默认存在),第二种方式是通过指定的字符索引进行。

s1 = Series([1,2,3,4,5],index=['a','b','c','d','e'])
>> 
a    1
b    2
c    3
d    4
e    5
dtype: int64

s1[0]
>> 1
s1['a']
>> 1

s1[1:3]
>> 
b    2
c    3
dtype: int64

s1['b':'c']
>>
b    2
c    3
dtype: int64

其实整数索引和字符索引,分别调用了s1.ilocs1.loc索引,其中iloc代表整数索引,如下代码:

s1.iloc[1:3]
>> 
b    2
c    3
dtype: int64

s1.loc['b':'c']
>>
b    2
c    3
dtype: int64

上面这种选则方法在DataFrame会有用到。对于Series数据,我们只要直接使用索引来获取部分元素即可。

DataFrame介绍

创建DataFrame数据

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

df = DataFrame(np.random.random((5,4)),index=['a','b','c','d','e'],columns=['A','B','C','D'])
>>
          A         B         C         D
a  0.095161  0.982524  0.253735  0.105706
b  0.795408  0.727552  0.183551  0.565994
c  0.968957  0.283453  0.630097  0.308991
d  0.310784  0.677971  0.417679  0.793429
e  0.042881  0.817054  0.508455  0.914367

上面的代码中,我们通过指定索引和标签来创建了一个DataFrame实例。如果不指定的话,默认就是整数索引和标签。

选择

由于DataFrmae有行列之分,于是我们如果只使用df['A']会无法判断是行还是列,于是就要与之前讲到的loc了。首先我们先看一下获取一列数据:

df = DataFrame(np.random.random((5,4)),index=['a','b','c','d','e'],columns=['A','B','C','D'])
>>
          A         B         C         D
a  0.798929  0.987169  0.495320  0.623631
b  0.000925  0.335466  0.117363  0.007925
c  0.730285  0.616586  0.972650  0.503028
d  0.783396  0.246152  0.084311  0.977647
e  0.105396  0.123413  0.284357  0.292990

df['A'] #获取一列的数据
>>
a    0.798929
b    0.000925
c    0.730285
d    0.783396
e    0.105396
Name: A, dtype: float64

df[df.columns[0:3]] #获取多列数据
>> 
          A         B         C
a  0.798929  0.987169  0.495320
b  0.000925  0.335466  0.117363
c  0.730285  0.616586  0.972650
d  0.783396  0.246152  0.084311
e  0.105396  0.123413  0.284357

"""
我们看一下df.columns是什么数据
"""

df.columns
>> Index(['A', 'B', 'C', 'D'], dtype='object')

下面我们来看一下如何来选择单行或者多行数据

df.loc['a'] #选择一行数据
>> 
A    0.798929
B    0.987169
C    0.495320
D    0.623631
Name: a, dtype: float64

df.loc['a':'c'] #选择多行数据
>>
          A         B         C         D
a  0.798929  0.987169  0.495320  0.623631
b  0.000925  0.335466  0.117363  0.007925
c  0.730285  0.616586  0.972650  0.503028

当然loc还是支持行和列的,我们接下去看。

下面我们来看一下如何选定具体的某行某列的数据,并且如何选中部分数据

df.loc['a','A']
>> 0.79892857875107204

df.loc['a':'c','A':'C']
>> 
          A         B         C
a  0.798929  0.987169  0.495320
b  0.000925  0.335466  0.117363
c  0.730285  0.616586  0.972650

对于上面的,我们需要注意顺序,即df.loc['a':'c','A':'C'],首先是行的索引,接着是列的索引。

缺失值和数据自动对齐

Pandas中最重要的一个功能是,它可以对不同索引的对象进行算术运算。比如将两个Series数据进行相加时,如果存在不同的索引,则结果是两个索引的并集,什么意思呢?通过例子看下:

s1 = Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])
>>
a    1
b    2
c    3
d    4
dtype: int64

s2 = Series([2, 3, 4, 5], index=['b', 'c', 'd', 'e'])
>> 
b    2
c    3
d    4
e    5
dtype: int64

s1+s2
>>
a    NaN
b    4.0
c    6.0
d    8.0
e    NaN
dtype: float64

以上代码中创建了两个s1s2两个Series序列,两者具有相同的索引['b', 'c', 'd'], 所以在进行相加时,相同索引上的值会相加,但不重叠的索引引入NaN值,也就是缺失值。

而缺失值会在运算中传播,所以最终结果也是NaN值。根据相同的索引进行自动计算,这就是自动对齐功能。

同样的规则,在DataFrame数据中也生效:

df1 = DataFrame(np.arange(9).reshape(3,3), columns=list('ABC'), index=list('abc'))
>> 
   A  B  C
a  0  1  2
b  3  4  5
c  6  7  8

df2 = DataFrame(np.arange(12).reshape(3,4),columns=list('ABDE'), index=list('bcd'))
>>
   A  B   D   E
b  0  1   2   3
c  4  5   6   7
d  8  9  10  11

df1+df2
>> 
      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

可以看到DataFrame的计算也会进行自动对齐操作,这个时候没有的行或者列会使用NaN值自动填充,而由于NaN值会传播,所以相加的结果也是NaN

当然我们在计算时,可以指定使用值来填充NaN值,然后带入计算过程,如下所示:

df1.add(df2,fill_value=0)
>>
      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

我们可以看到结果中仍有NaN,这是因为在那几个位置中df1和df2都没有定义,所以是NaN

常用统计

对于一些数据,我们可以直接使用describe来得到一些常用的统计信息。

df1 = DataFrame(np.arange(9).reshape(3,3), columns=list('ABC'), index=list('abc'))

df1.describe()
>>
         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

可以看到默认是按列来统计的,我们看一下每一个参数是什么意思。

  • count 元素值的数量;
  • mean 平均值;
  • std 标准差;
  • min 最小值;
  • 25% 下四分位数;
  • 50% 中位数;
  • 75% 上四分位数;
  • max 最大值;

按行或列进行运算

除了上面讲到的常用的运算,我们还可以自定义一些运算,如下面我们可以自定义一下极差的运算

df1 = DataFrame(np.arange(9).reshape(3,3), columns=list('ABC'), index=list('abc'))
>>
   A  B  C
a  0  1  2
b  3  4  5
c  6  7  8

f = lambda x: x.max() - x.min() #这里也可以不使用匿名函数

def f(x):
    return x.max()-x.min()

df1.apply(f)
>>
A    6
B    6
C    6
dtype: int64

df1.apply(f,axis=1)
>>
a    2
b    2
c    2
dtype: int64

数据合并和分组

数组合并

有的时候需要合并两个DataFrame数据,合并数据的方式主要有两种,一种简单的进行拼接(列是一样的,直接按照行接下去),另一种是根据列名类像数据库表查询一样进行合并(有一个或多个相同的列,按照这些列进行合并)。

这两种操作可以分别通过调用pandas.concatpandas.merge方法实现。

首先看一下pandas.concat

df1 = DataFrame(np.random.randn(3, 3))
>>
          0         1         2
0  0.506929  0.482098  0.142396
1 -1.431190  0.527396  0.784056
2 -1.184716  2.082178  0.666990

df2 = DataFrame(np.random.randn(3, 3), index=[5, 6, 7])
>>
          0         1         2
5  0.611993 -0.264532 -0.022721
6  0.436712 -0.365940  1.430864
7 -0.646884  0.368955 -0.704303

pd.concat([df1,df2])
>>
          0         1         2
0  0.506929  0.482098  0.142396
1 -1.431190  0.527396  0.784056
2 -1.184716  2.082178  0.666990
5  0.611993 -0.264532 -0.022721
6  0.436712 -0.365940  1.430864
7 -0.646884  0.368955 -0.704303

接着我们来看一下pandas.merge的操作,使用这个操作必须有一个相同的列,如下面;例子中有相同的列course

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

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

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

分组

在 Pandas 中,也支持类似于数据库查询语句GROUP BY的功能,也就是按照某列进行分组,然后再分组上进行一些计算操作,假如我们有如下的数据集,那么如何计算其中user_id5348的用户的学习时间呢?

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

>>
   course  minutes  user_id
0      12        9     5348
1      45       36       13
2      23       45     5348

我们可以首先筛选出所有user_id为5348的行,我们通过df[df['user_id']==5348]来进行寻找出需要用的行,然后找出时间的列,然后进行求和统计,我们可以看一下下面的具体代码。

df['user_id']==5348
>>
0     True
1    False
2     True
Name: user_id, dtype: bool

df[df['user_id']==5348]
>>
   course  minutes  user_id
0      12        9     5348
2      23       45     5348

df[df['user_id']==5348]['minutes'].sum()
>> 54

当然,我们可以使用group_by进行直接求和。

df.groupby('user_id').sum()
>> 
         course  minutes
user_id                 
13           45       36
5348         35       54

时间数据处理

我们这里创建一个简单的时间序列,在演示一下基本的用法:

from datetime import datetime

dates = [datetime(2018, 1, 1), datetime(2018, 1, 2), datetime(2018, 1, 3), datetime(2018, 1,4)]

ts = Series(np.random.randn(4), index=dates)

>> 
2018-01-01   -1.959113
2018-01-02   -1.637479
2018-01-03    0.833776
2018-01-04    0.546243
dtype: float64

我们有多种方式可以选择元素,只要传入一个可以被pandas识别的日期字符串就可以了。

ts['2018/1/1']
>> -1.9591133192825554

ts['2018-1-1']
>> -1.9591133192825554

上面我们生产日期的方式是需要一个一个输入的,当然除了这种方式,还可以使用pandas.data_range来完成,该函数主要有下面的几个参数:

  • start: 指定了日期范围的起始时间;
  • end: 指定了日期范围的结束时间;
  • periods: 指定了间隔范围,如果只是指定了startend日期的其中一个,则需要改参数;
  • freq: 指定了日期频率,比如D代表每天,H代表每小时,M代表月,这些频率字符前也可以指定一个整数,代表具体多少天,多少小时,比如5D代表5天。还有一些其他的频率字符串,比如MS代表每月第一天,BM代表每月最后一个工作日,或者是频率组合字符串,比如 1h30min代表1 小时 30 分钟
pd.date_range('2018','2019',freq='M')
>>
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')

当然,我们有时候还需要进行采样,比如原始数据是每分钟进行采样,我们想要变成每天进行采样,于是就可以使用resample方法。

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

ts = Series(np.arange(len(dates)), index=dates)

ts.size
>> 48

我们先使用resample('D')方法指定了按天统计,接着使用sum方法指定了最终数据是按天的所有数据的和进行统计。

ts.resample('D').sum()
>>
2018-01-01    276
2018-01-02    852
Freq: D, dtype: int64

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

ts.resample('D').mean()
>> 
2018-01-01    11.5
2018-01-02    35.5
Freq: D, dtype: float64

当然,resample('D')除了可以把高频转为低频,还可以把低频转为高频,默认情况下 Pandas 会引入NaN值,因为没办法从低频率的数据计算出高频率的数据,但可以通过fill_method参数指定插值方式:

ts.resample('D').mean().resample('H').ffill()
>>
2018-01-01 00:00:00    11.5
2018-01-01 01:00:00    11.5
....
2018-01-01 20:00:00    11.5
2018-01-01 21:00:00    11.5
2018-01-01 22:00:00    11.5
2018-01-01 23:00:00    11.5
2018-01-02 00:00:00    35.5
Freq: H, dtype: float64

注意上面的ffill表示用前面的值代替NaN

  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: