首页 > 其他分享 >c、Pandas

c、Pandas

时间:2023-08-12 10:22:23浏览次数:65  
标签:df dtype NaN DataFrame pd Pandas Out

Pandas

img

官网:http://pandas.pydata.org/

文档:

中文文档:https://www.pypandas.cn/docs/

Pandas是一个高性能的数据操作和分析工具。它在Numpy的基础上,提供了一种高效的DataFrame数据结构,使得在Python中进行数据清洗和分析非常快捷。Pandas采用了很多Numpy的代码风格,但最大的不同在于pandas主要用来处理表格型或异质型数据,而Numpy则相反,它更适合处理同质并且是数值类型的数据。事实上大多数时候,我们使用Pandas多于Numpy。

Pandas最初由AQR Capital Management于2008年4月开发,并于2009年底开源出来,目前由专注于Python数据包开发的PyData开发团队继续开发和维护,属于PyData项目的一部分。Pandas最初被作为金融数据分析工具而开发出来,因此,pandas为时间序列分析提供了很好的支持。 Pandas的名称来自于面板数据(panel data)和python数据分析(data analysis)。面板数据是经济学中关于多维数据集的一个术语,在Pandas中也提供了panel的数据类型。

Pandas的主要特点:

  • 快速高效的DataFrame对象,具有默认和自定义的索引。
  • 将数据从不同文件格式加载到内存中的数据对象的工具。
  • 丢失数据的数据对齐和综合处理。
  • 重组和摆动日期集。
  • 基于标签的切片,索引和大数据集的子集。
  • 可以删除或插入来自数据结构的列。
  • 按数据分组进行聚合和转换。
  • 高性能合并和数据加入。
  • 时间序列功能。

通常我们都使用Anaconda发行版安装Pandas,如果你非要自己安装,可以使用:

pip install pandas
conda install pandas
python3 -m pip install --upgrade pandas

对于Linux,比如Ubuntu,可以使用下面的方法安装,但可能出现各种依赖缺失或者安装错误:

sudo apt-get install python-numpy python-scipy python-matplotlib ipython python-pandas python-sympy python-nose

安装完Pandas后,我们就可以在notebook中导入它了,通常我们会使用下面的国际惯例进行导入:

import pandas as pd

有时候,我们也会将它包含的两个重要数据结构也单独导入:

from pandas import Series, DataFrame

可以如下查看当前Pandas的版本信息:

pd.__version__

Series

Pandas的核心是三大数据结构:Series、DataFrame和Index。绝大多数操作都是围绕这三种结构进行的。

pd.Series(data, index=index)

其中,index 是一个可选参数,data 参数支持多种数据类型。

Series是一个一维的数组对象,它包含一个值序列和一个对应的索引序列。 Numpy的一维数组通过隐式定义的整数索引获取元素值,而Series用一种显式定义的索引与元素关联。显式索引让Series对象拥有更强的能力,索引也不再仅仅是整数,还可以是别的类型,比如字符串,索引也不需要连续,也可以重复,自由度非常高。

最基本的生成方式是使用Series构造器:

In [1]: import pandas as pd

In [4]: s = pd.Series([7,-3,4,-2])

In [5]: s
Out[5]:
0    7
1   -3
2    4
3   -2
dtype: int64

创建

import pandas as pd
import numpy as np

# 从ndarray创建一个系列
data = np.array(['a','b','c','d'])
s = pd.Series(data)
s = pd.Series(data,index=[100,101,102,103])
# 从字典创建一个系列	
data = {'a' : 0., 'b' : 1., 'c' : 2.}
s = pd.Series(data)
# 从标量创建一个系列
s = pd.Series(5, index=[0, 1, 2, 3])

打印的时候,自动对齐了,看起来比较美观。左边是索引,右边是实际对应的值。默认的索引是0到N-1(N是数据的长度)。可以通过values和index属性分别获取Series对象的值和索引:

In [5]: s.dtype
Out[5]: dtype('int64')

In [6]: s.values
Out[6]: array([ 7, -3,  4, -2], dtype=int64)

In [7]: s.index
Out[7]: RangeIndex(start=0, stop=4, step=1)

可以在创建Series对象的时候指定索引:

In [8]: s2 = pd.Series([7,-3,4,-2], index=['d','b','a','c'])

In [9]: s2
Out[9]:
d    7
b   -3
a    4
c   -2
dtype: int64

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

In [4]: pd.Series(5, index=list('abcde'))
Out[4]:
a    5
b    5
c    5
d    5
e    5
dtype: int64

In [5]: pd.Series({2:'a',1:'b',3:'c'}, index=[3,2]) # 通过index筛选结果
Out[5]:
3    c
2    a
dtype: object

也可以在后期,直接修改index:

In [33]: s
Out[33]:
0    7
1   -3
2    4
3   -2
dtype: int64

In [34]: s.index = ['a','b','c','d']

In [35]: s
Out[35]:
a    7
b   -3
c    4
d   -2
dtype: int64

类似Python的列表和Numpy的数组,Series也可以通过索引获取对应的值:

In [11]: s2['a']
Out[11]: 4

In [12]: s2[['c','a','d']]
Out[12]:
c   -2
a    4
d    7
dtype: int64

也可以对Seires执行一些类似Numpy的通用函数操作:

In [13]: s2[s2>0]
Out[13]:
d    7
a    4
dtype: int64

In [14]: s2*2
Out[14]:
d    14
b    -6
a     8
c    -4
dtype: int64

In [15]: import numpy as np

In [16]: np.exp(s2)
Out[16]:
d    1096.633158
b       0.049787
a      54.598150
c       0.135335
dtype: float64

因为索引可以是字符串,所以从某个角度看,Series又比较类似Python的有序字典,所以可以使用in操作:

In [17]: 'b' in s2
Out[17]: True

In [18]: 'e'in s2
Out[18]: False

自然,我们也会想到使用Python的字典来创建Series:

In [19]: dic = {'beijing':35000,'shanghai':71000,'guangzhou':16000,'shenzhen':5000}

In [20]: s3=pd.Series(dic)

In [21]: s3
Out[21]:
beijing      35000
shanghai     71000
guangzhou    16000
shenzhen     5000
dtype: int64

In [14]: s3.keys() # 自然,具有类似字典的方法
Out[14]: Index(['beijing', 'shanghai', 'guangzhou', 'shenzhen'], dtype='object')

In [15]: s3.items()
Out[15]: <zip at 0x1a5c2d88c88>

In [16]: list(s3.items())
Out[16]:
[('beijing', 35000),
 ('shanghai', 71000),
 ('guangzhou', 16000),
 ('shenzhen', 5000)]

In [18]: s3['changsha'] = 20300

我们看下面的例子:

In [22]: city = ['nanjing', 'shanghai','guangzhou','beijing']

In [23]: s4=pd.Series(dic, index=city)
In [24]: s4
Out[24]:
nanjing          NaN
shanghai     71000.0
guangzhou    16000.0
beijing      35000.0
dtype: float64

city列表中,多了‘nanjing’,但少了‘shenzhen’。Pandas会依据city中的关键字去dic中查找对应的值,因为dic中没有‘nanjing’,这个值缺失,所以以专门的标记值NaN表示。因为city中没有‘shenzhen’,所以在s4中也不会存在‘shenzhen’这个条目。可以看出,索引很关键,在这里起到了决定性的作用。

在Pandas中,可以使用isnull和notnull函数来检查缺失的数据:

In [25]: pd.isnull(s4)
Out[25]:
nanjing       True
shanghai     False
guangzhou    False
beijing      False
dtype: bool

In [26]: pd.notnull(s4)
Out[26]:
nanjing      False
shanghai      True
guangzhou     True
beijing       True
dtype: bool

In [27]: s4.isnull()
Out[27]:
nanjing       True
shanghai     False
guangzhou    False
beijing      False
dtype: bool

可以为Series对象和其索引设置name属性,这有助于标记识别:

In [29]: s4.name = 'people'

In [30]: s4.index.name= 'city'

In [31]: s4
Out[31]:
city
nanjing          NaN
shanghai     71000.0
guangzhou    16000.0
beijing      35000.0
Name: people, dtype: float64

In [32]: s4.index
Out[32]: Index(['nanjing', 'shanghai', 'guangzhou', 'beijing'], dtype='object', name='city')

DataFrame

DataFrame是Pandas的核心数据结构,表示的是二维的矩阵数据表,类似关系型数据库的结构,每一列可以是不同的值类型,比如数值、字符串、布尔值等等。DataFrame既有行索引,也有列索引,它可以被看做为一个共享相同索引的Series的字典。

创建DataFrame对象的方法有很多,最常用的是利用包含等长度列表或Numpy数组的字典来生成。可以查看DataFrame对象的columns和index属性。

In [37]: data = {'state':['beijing','beijing','beijing','shanghai','shanghai','shanghai'],
    ...: 'year':[2000,2001,2002,2001,2002,2003],
    ...: 'pop':[1.5, 1.7,3.6,2.4,2.9,3.2
    ...: ]}

In [38]: f = pd.DataFrame(data)

In [39]: f
Out[39]:
      state  year  pop
0   beijing  2000  1.5
1   beijing  2001  1.7
2   beijing  2002  3.6
3  shanghai  2001  2.4
4  shanghai  2002  2.9
5  shanghai  2003  3.2

In [61]: f.columns
Out[61]: Index(['state', 'year', 'pop'], dtype='object')

In [62]: f.index
Out[62]: RangeIndex(start=0, stop=6, step=1)

In [10]: f.dtypes
Out[10]:
state     object
year       int64
pop      float64
dtype: object

In [11]: f.values  # 按行查看
Out[11]:
array([['beijing', 2000, 1.5],
       ['beijing', 2001, 1.7],
       ['beijing', 2002, 3.6],
       ['shanghai', 2001, 2.4],
       ['shanghai', 2002, 2.9],
       ['shanghai', 2003, 3.2]], dtype=object)

上面自动生成了0-5的索引。

可以使用head方法查看DataFrame对象的前5行,用tail方法查看后5行。或者head(3),tail(3)指定查看行数:

In [40]: f.head()
Out[40]:
      state  year  pop
0   beijing  2000  1.5
1   beijing  2001  1.7
2   beijing  2002  3.6
3  shanghai  2001  2.4
4  shanghai  2002  2.9

In [41]: f.tail()
Out[41]:
      state  year  pop
1   beijing  2001  1.7
2   beijing  2002  3.6
3  shanghai  2001  2.4
4  shanghai  2002  2.9
5  shanghai  2003  3.2

DataFrame对象中的state/year/pop,其实就是列索引,可以在创建的时候使用参数名columns指定它们的先后顺序:

In [44]: pd.DataFrame(data, columns=['year','state','pop'])
Out[44]:
   year     state  pop
0  2000   beijing  1.5
1  2001   beijing  1.7
2  2002   beijing  3.6
3  2001  shanghai  2.4
4  2002  shanghai  2.9
5  2003  shanghai  3.2

当然,也可以使用参数名index指定行索引:

In [45]: f2 = pd.DataFrame(data, columns=['year','state','pop'],index=['a','b','c','d','e','f'])

In [47]: f2
Out[47]:
   year     state  pop
a  2000   beijing  1.5
b  2001   beijing  1.7
c  2002   beijing  3.6
d  2001  shanghai  2.4
e  2002  shanghai  2.9
f  2003  shanghai  3.2

可以使用columns列索引来检索一列:

In [49]: f2['year']
Out[49]:
a    2000
b    2001
c    2002
d    2001
e    2002
f    2003
Name: year, dtype: int64

In [52]: f2.state  # 属性的形式来检索。这种方法bug多,比如属性名不是纯字符串,或者与其它方法同名
Out[52]:
a     beijing
b     beijing
c     beijing
d    shanghai
e    shanghai
f    shanghai
Name: state, dtype: object

但是检索一行却不能通过f2['a']这种方式,而是需要通过loc方法进行选取:

In [53]: f2.loc['a']
Out[53]:
year        2000
state    beijing
pop          1.5
Name: a, dtype: object

当然,可以给DataFrame对象追加列:

In [54]: f2['debt'] = 12

In [55]: f2
Out[55]:
   year     state  pop  debt
a  2000   beijing  1.5    12
b  2001   beijing  1.7    12
c  2002   beijing  3.6    12
d  2001  shanghai  2.4    12
e  2002  shanghai  2.9    12
f  2003  shanghai  3.2    12

In [56]: f2['debt'] = np.arange(1,7)

In [57]: f2
Out[57]:
   year     state  pop  debt
a  2000   beijing  1.5     1
b  2001   beijing  1.7     2
c  2002   beijing  3.6     3
d  2001  shanghai  2.4     4
e  2002  shanghai  2.9     5
f  2003  shanghai  3.2     6

In [58]: val = pd.Series([1,2,3],index = ['c','d','f'])

In [59]: f2['debt'] = val

In [60]: f2  # 缺失值以NaN填补
Out[60]:
   year     state  pop  debt
a  2000   beijing  1.5   NaN
b  2001   beijing  1.7   NaN
c  2002   beijing  3.6   1.0
d  2001  shanghai  2.4   2.0
e  2002  shanghai  2.9   NaN
f  2003  shanghai  3.2   3.0

那么如何给DataFrame追加行呢?

>>> data = {'state':['beijing','beijing','beijing','shanghai','shanghai','shanghai'],
    ...: 'year':[2000,2001,2002,2001,2002,2003],
    ...: 'pop':[1.5, 1.7,3.6,2.4,2.9,3.2
    ...: ]}
>>> df = pd.DataFrame(data,index=list('abcdef'))
>>> df

    state   year    pop
a   beijing 2000    1.5
b   beijing 2001    1.7
c   beijing 2002    3.6
d   shanghai    2001    2.4
e   shanghai    2002    2.9
f   shanghai    2003    3.2

>>> df1 = df.loc['a']
>>> df1

state    beijing
year        2000
pop          1.5
Name: a, dtype: object

>>> df.append(df1)

state   year    pop
a   beijing 2000    1.5
b   beijing 2001    1.7
c   beijing 2002    3.6
d   shanghai    2001    2.4
e   shanghai    2002    2.9
f   shanghai    2003    3.2
a   beijing 2000    1.5

可以使用del方法删除指定的列:

In [63]: f2['new'] = f2.state=='beijing'

In [64]: f2
Out[64]:
   year     state  pop  debt    new
a  2000   beijing  1.5   NaN   True
b  2001   beijing  1.7   NaN   True
c  2002   beijing  3.6   1.0   True
d  2001  shanghai  2.4   2.0  False
e  2002  shanghai  2.9   NaN  False
f  2003  shanghai  3.2   3.0  False

In [65]: del f2.new   # 要注意的是我们有时候不能这么调用f2的某个列,并执行某个操作。这是个坑。
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-65-03e4ec812cdb> in <module>()
----> 1 del f2.new

AttributeError: new

In [66]: del f2['new']

In [67]: f2.columns
Out[67]: Index(['year', 'state', 'pop', 'debt'], dtype='object')

需要注意的是:从DataFrame中选取的列是数据的视图,而不是拷贝。因此,对选取列的修改会反映到DataFrame上。如果需要复制,应当使用copy方法。

可以使用类似Numpy的T属性,将DataFrame进行转置:

In [68]: f2.T
Out[68]:
             a        b        c         d         e         f
year      2000     2001     2002      2001      2002      2003
state  beijing  beijing  beijing  shanghai  shanghai  shanghai
pop        1.5      1.7      3.6       2.4       2.9       3.2
debt       NaN      NaN        1         2       NaN         3

DataFrame对象同样具有列名、索引名,也可以查看values:

In [70]: f2.index.name = 'order';f2.columns.name='key'

In [71]: f2
Out[71]:
key    year     state  pop  debt
order
a      2000   beijing  1.5   NaN
b      2001   beijing  1.7   NaN
c      2002   beijing  3.6   1.0
d      2001  shanghai  2.4   2.0
e      2002  shanghai  2.9   NaN
f      2003  shanghai  3.2   3.0

In [72]: f2.values
Out[72]:
array([[2000, 'beijing', 1.5, nan],
       [2001, 'beijing', 1.7, nan],
       [2002, 'beijing', 3.6, 1.0],
       [2001, 'shanghai', 2.4, 2.0],
       [2002, 'shanghai', 2.9, nan],
       [2003, 'shanghai', 3.2, 3.0]], dtype=object)

最后,DataFrame有一个Series所不具备的方法,那就是info!通过这个方法,可以看到DataFrame的一些整体信息情况:

In [73]: f.info()
Out[73]:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 3 columns):
state    6 non-null object
year     6 non-null int64
pop      6 non-null float64
dtypes: float64(1), int64(1), object(1)
memory usage: 224.0+ bytes

Index

Pandas中的索引对象Index用于存储轴标签和其它元数据。索引对象是不可变的,用户无法修改它。

In [73]: obj = pd.Series(range(3),index = ['a','b','c'])

In [74]: index = obj.index

In [75]: index
Out[75]: Index(['a', 'b', 'c'], dtype='object')

In [76]: index[1:]
Out[76]: Index(['b', 'c'], dtype='object')

In [77]: index[1] = 'f'  # TypeError

In [8]: index.size
Out[8]: 3

In [9]: index.shape
Out[9]: (3,)

In [10]: index.ndim
Out[10]: 1

In [11]: index.dtype
Out[11]: dtype('O')

索引对象的不可变特性,使得在多种数据结构中分享索引对象更安全:

In [78]: labels = pd.Index(np.arange(3))

In [79]: labels
Out[79]: Int64Index([0, 1, 2], dtype='int64')

In [80]: obj2 = pd.Series([2,3.5,0], index=labels)

In [81]: obj2
Out[81]:
0    2.0
1    3.5
2    0.0
dtype: float64

In [82]: obj2.index is labels
Out[82]: True

索引对象,本质上也是一个容器对象,所以可以使用Python的in操作:

In [84]: f2
Out[84]:
key    year     state  pop  debt
order
a      2000   beijing  1.5   NaN
b      2001   beijing  1.7   NaN
c      2002   beijing  3.6   1.0
d      2001  shanghai  2.4   2.0
e      2002  shanghai  2.9   NaN
f      2003  shanghai  3.2   3.0

In [86]: 'c' in f2.index
Out[86]: True

In [88]: 'pop' in f2.columns
Out[88]: True

而且最关键的是,pandas的索引对象可以包含重复的标签:

In [89]: dup_lables = pd.Index(['foo','foo','bar','bar'])

In [90]: dup_lables
Out[90]: Index(['foo', 'foo', 'bar', 'bar'], dtype='object')

那么思考一下,DataFrame对象可不可以有重复的columns或者index呢?

可以的!但是请尽量不要这么做!:

In [91]: f2.index = ['a']*6

In [92]: f2
Out[92]:
key  year     state  pop  debt
a    2000   beijing  1.5   NaN
a    2001   beijing  1.7   NaN
a    2002   beijing  3.6   1.0
a    2001  shanghai  2.4   2.0
a    2002  shanghai  2.9   NaN
a    2003  shanghai  3.2   3.0

In [93]: f2.loc['a']
Out[93]:
key  year     state  pop  debt
a    2000   beijing  1.5   NaN
a    2001   beijing  1.7   NaN
a    2002   beijing  3.6   1.0
a    2001  shanghai  2.4   2.0
a    2002  shanghai  2.9   NaN
a    2003  shanghai  3.2   3.0

In [94]: f2.columns = ['year']*4

In [95]: f2
Out[95]:
   year      year  year  year
a  2000   beijing   1.5   NaN
a  2001   beijing   1.7   NaN
a  2002   beijing   3.6   1.0
a  2001  shanghai   2.4   2.0
a  2002  shanghai   2.9   NaN
a  2003  shanghai   3.2   3.0

In [96]: f2.index.is_unique  # 可以使用这个属性来判断是否存在重复的索引
Out[96]: False

index对象也可以进行集合的交、并、差和异或运算,类似Python的标准set数据结构。

In[35]: indA = pd.Index([1, 3, 5, 7, 9])         
		indB = pd.Index([2, 3, 5, 7, 11]) 
 
In[36]: indA & indB  # 交集  
Out[36]: Int64Index([3, 5, 7], dtype='int64') 
 
In[37]: indA | indB  # 并集 
Out[37]: Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64') 
 
In[38]: indA ^ indB  # 异或 
Out[38]: Int64Index([1, 2, 9, 11], dtype='int64')

数据取值与选择

从简单的一维 Series 对象开始,然后再用比较复杂的二维 DataFrame 对象进行演示。

1、Series数据选择方法

Series 对象提供了键值对的映射:

In[1]: import pandas as pd        
	   data = pd.Series([0.25, 0.5, 0.75, 1.0],                         
	   index=['a', 'b', 'c', 'd'])        
	   data 
 
Out[1]: a    0.25
		b    0.50         
		c    0.75         
		d    1.00         
		dtype: float64 
 
In[2]: data['b'] 
Out[2]: 0.5

还可以用 Python 字典的表达式和方法来检测键 / 索引和值:

In[3]: 'a' in data 
Out[3]: True 
 
In[4]: data.keys() 
Out[4]: Index(['a', 'b', 'c', 'd'], dtype='object') 
 
In[5]: list(data.items()) 
Out[5]: [('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]

Series 对象还可以用字典语法调整数据。就像你可以通过增加新的键扩展字典一样,你也可以通过增加新的索引值扩展 Series:

In[6]: data['e'] = 1.25
	   data 

Out[6]: a    0.25         
		b    0.50         
		c    0.75        
        d    1.00        
        e    1.25         
        dtype: float64

Series 对象的可变性是一个非常方便的特性:Pandas 在底层已经为可能发生的内存布局和数据复制自动决策。

Series 不仅有着和字典一样的接口,而且还具备和 NumPy 数组一样的数组数据选择功能, 包括索引、掩码、花哨的索引等操作,具体示例如下所示:

In[7]: # 将显式索引作为切片        
	   data['a':'c'] 
Out[7]: a    0.25        
		b    0.50        
        c    0.75        
        dtype: float64 
 
In[8]: # 将隐式整数索引作为切片        
	   data[0:2] 
Out[8]: a    0.25
		b    0.50         
		dtype: float64 
 
In[9]: # 掩码        
	   data[(data > 0.3) & (data < 0.8)] 
Out[9]: b    0.50      
		c    0.75       
        dtype: float64 
 
In[10]: # 花哨的索引        
		data[['a', 'e']] 
Out[10]: a    0.25        
		 e    1.25
         dtype: float64

在以上示例中,切片是绝大部分混乱之源。需要注意的是,当使用显式索引(即 data['a':'c'])作切片时,结果包含最后一个索引;而当使用隐式索引(即 data[0:2]) 作切片时,结果不包含最后一个索引。

由于整数索引很容易造成混淆,所以 Pandas 提供了一些索引器(indexer)属性来作为取值的方法。它们不是 Series 对象的函数方法,而是暴露切片接口的属性。

第一种索引器是 loc 属性,表示取值和切片都是显式的:

In[14]: data.loc[1] 
Out[14]: 'a' 

In[15]: data.loc[1:3] 
Out[15]: 1    a     
		 3    b        
         dtype: object

第二种是 iloc 属性,表示取值和切片都是 Python 形式的隐式索引:

In[16]: data.iloc[1] 
Out[16]: 'b' 

In[17]: data.iloc[1:3] 
Out[17]: 3    b      
		 5    c        
         dtype: object

第三种取值属性是 ix,它是前两种索引器的混合形式,在 Series 对象中 ix 等价于标准的 [](Python 列表)取值方式。ix 索引器主要用于 DataFrame 对象,后面将会介绍。
Python 代码的设计原则之一是“显式优于隐式”。使用 loc 和 iloc 可以让代码更容易维护, 可读性更高。特别是在处理整数索引的对象时,我强烈推荐使用这两种索引器。它们既可以让代码阅读和理解起来更容易,也能避免因误用索引 / 切片而产生的小bug。

2、DataFrame数据选择方法

DataFrame 在有些方面像二维或结构化数组,在有些方面又像一个共享索引的若干 Series 对象构成的字典。这两种类比可以帮助我们更好地掌握这种数据结构的数据选择方法。

第一种类比是把 DataFrame 当作一个由若干 Series 对象构成的字典。让我们用之前的美国五州面积与人口数据来演示:

In[18]: area = pd.Series({'California': 423967, 'Texas': 695662, 'New York': 141297, 'Florida': 170312, 'Illinois': 149995}) 
		pop = pd.Series({'California': 38332521, 'Texas': 26448193, 'New York': 19651127, 'Florida': 19552860, 'Illinois': 12882135})  
        data = pd.DataFrame({'area':area, 'pop':pop})        
        data 
Out[18]:             area    pop          
		California  423967  38332521     
        Florida     170312  19552860       
        Illinois    149995  12882135    
        New York    141297  19651127      
        Texas       695662  26448193

两个 Series 分别构成 DataFrame 的一列,可以通过对列名进行字典形式(dictionary-style) 的取值获取数据:

In[19]: data['area'] 

Out[19]: California    423967  
		 Florida       170312        
         Illinois      149995         
         New York      141297         
         Texas         695662        
         Name: area, dtype: int64

同样,也可以用属性形式(attribute-style)选择纯字符串列名的数据:

In[20]: data.area 

Out[20]: California    423967       
		 Florida       170312       
         Illinois      149995        
         New York      141297         
         Texas         695662      
         Name: area, dtype: int64

对同一个对象进行属性形式与字典形式的列数据,结果是相同的:

In[21]: data.area is data['area'] 
Out[21]: True

虽然属性形式的数据选择方法很方便,但是它并不是通用的。如果列名不是纯字符串,或者列名与 DataFrame 的方法同名,那么就不能用属性索引。例如,DataFrame 有一个 pop() 方法,如果用 data.pop 就不会获取 'pop' 列,而是显示为方法:

In[22]: data.pop is data['pop'] 
Out[22]: False

另外,还应该避免对用属性形式选择的列直接赋值(即可以用 data['pop'] = z,但不要用 data.pop = z)。

和前面介绍的 Series 对象一样,还可以用字典形式的语法调整对象,如果要增加一列可以这样做:

In[23]: data['density'] = data['pop'] / data['area'] 
		data 

Out[23]:             area    pop       density          
		California  423967  38332521   90.413926    
        Florida     170312  19552860  114.806121       
        Illinois    149995  12882135   85.883763         
        New York    141297  19651127  139.076746      
        Texas       695662  26448193   38.018740

这里演示了两个 Series 对象算术运算的简便语法

前面曾提到,可以把 DataFrame 看成是一个增强版的二维数组,用 values 属性按行查看数组数据:

In[24]: data.values 

Out[24]: array([[  4.23967000e+05,   3.83325210e+07,   9.04139261e+01],  
				[  1.70312000e+05,   1.95528600e+07,   1.14806121e+02],    
                [  1.49995000e+05,   1.28821350e+07,   8.58837628e+01],        
                [  1.41297000e+05,   1.96511270e+07,   1.39076746e+02],        
                [  6.95662000e+05,   2.64481930e+07,   3.80187404e+01]])

理解了这一点,就可以把许多数组操作方式用在 DataFrame 上。例如,可以对 DataFrame进行行列转置:

In[25]: data.T 

Out[25]:          California    Florida       Illinois      New York      Texas
		area     4.239670e+05  1.703120e+05  1.499950e+05  1.412970e+05  6.956620e+05 
		pop      3.833252e+07  1.955286e+07  1.288214e+07  1.965113e+07  2.644819e+07 
		density  9.041393e+01  1.148061e+02  8.588376e+01  1.390767e+02  3.801874e+01

通过字典形式对列进行取值显然会限制我们把 DataFrame 作为 NumPy 数组可以获得的能力,尤其是当我们在 DataFrame 数组中使用单个行索引获取一行数据时:

In[26]: data.values[0] 
Out[26]: array([  4.23967000e+05,   3.83325210e+07,   9.04139261e+01])

而获取一列数据就需要向 DataFrame 传递单个列索引:

In[27]: data['area'] 

Out[27]: California    423967          
		 Florida       170312          
		 Illinois      149995          
		 New York      141297          
		 Texas         695662          
		 Name: area, dtype: int64 

因此,在进行数组形式的取值时,我们就需要用另一种方法——前面介绍过的 Pandas 索引器 loc、iloc 和 ix 了。通过 iloc 索引器,我们就可以像对待 NumPy 数组一样索引 Pandas 的底层数组(Python 的隐式索引),DataFrame 的行列标签会自动保留在结果中:

In[28]: data.iloc[:3, :2] 
Out[28]:             area    pop         
		California  423967  38332521         
        Florida     170312  19552860         
        Illinois    149995  12882135 

In[29]: data.loc[:'Illinois', :'pop'] 
Out[29]:             area    pop          
		California  423967  38332521      
        Florida     170312  19552860      
        Illinois    149995  12882135

使用 ix 索引器可以实现一种混合效果:

In[30]: data.ix[:3, :'pop'] 
Out[30]:             area    pop      
		California  423967  38332521         
        Florida     170312  19552860        
        Illinois    149995  12882135

需要注意的是,ix 索引器对于整数索引的处理和之前在 Series 对象中介绍的一样,都容易让人混淆。
任何用于处理 NumPy 形式数据的方法都可以用于这些索引器。例如,可以在 loc 索引器中结合使用掩码与花哨的索引方法:

In[31]: data.loc[data.density > 100, ['pop', 'density']] 
Out[31]:           pop       density         
		Florida   19552860  114.806121          
		New York  19651127  139.076746

任何一种取值方法都可以用于调整数据,这一点和 NumPy 的常用方法是相同的:

In[32]: data.iloc[0, 2] = 90        
		data 
Out[32]:             area    pop       density         
		California  423967  38332521   90.000000         
        Florida     170312  19552860  114.806121         
        Illinois    149995  12882135   85.883763        
        New York    141297  19651127  139.076746       
        Texas       695662  26448193   38.018740

如果你想熟练使用 Pandas 的数据操作方法,我建议你花点时间在一个简单的 DataFrame 上练习不同的取值方法,包括查看索引类型、切片、掩码和花哨的索引操作。

还有一些取值方法和前面介绍过的方法不太一样。它们虽然看着有点奇怪,但是在实践中还是很好用的。首先,如果对单个标签取值就选择列,而对多个标签用切片就选择行:

In[33]: data['Florida':'Illinois'] 
Out[33]:           area    pop       density       
		Florida   170312  19552860  114.806121         
        Illinois  149995  12882135   85.883763

切片也可以不用索引值,而直接用行数来实现:

In[34]: data[1:3] 
Out[34]:           area    pop       density       
		Florida   170312  19552860  114.806121     
        Illinois  149995  12882135   85.883763

与之类似,掩码操作也可以直接对每一行进行过滤,而不需要使用 loc 索引器:

In[35]: data[data.density > 100] 
Out[35]:           area    pop       density     
		Florida   170312  19552860  114.806121      
        New York  141297  19651127  139.076746

这两种操作方法其实与 NumPy 数组的语法类似,虽然它们与 Pandas 的操作习惯不太一致, 但是在实践中非常好用。

重建索引

reindex方法用于重新为Pandas对象设置新索引。这不是就地修改,而是会参照原有数据,调整顺序。

In [96]: obj=pd.Series([4.5,7.2,-5.3,3.6],index = ['d','b','a','c'])

In [97]: obj
Out[97]:
d    4.5
b    7.2
a   -5.3
c    3.6
dtype: float64

reindex会按照新的索引进行排列,不存在的索引将引入缺失值:

In [99]: obj2 = obj.reindex(list('abcde'))

In [100]: obj2
Out[100]:
a   -5.3
b    7.2
c    3.6
d    4.5
e    NaN
dtype: float64

也可以为缺失值指定填充方式method参数,比如ffill表示向前填充,bfill表示向后填充:

In [101]: obj3 = pd.Series(['blue','purple','yellow'],index = [0,2,4])

In [102]: obj3
Out[102]:
0      blue
2    purple
4    yellow
dtype: object

In [103]: obj3.reindex(range(6),method='ffill')
Out[103]:
0      blue
1      blue
2    purple
3    purple
4    yellow
5    yellow
dtype: object

对于DataFrame这种二维对象,如果执行reindex方法时只提供一个列表参数,则默认是修改行索引。可以用关键字参数columns指定修改的是列索引:

In [104]: f = pd.DataFrame(np.arange(9).reshape((3,3)),index=list('acd'),columns=['beijing','shanghai','guangzhou'])

In [105]: f
Out[105]:
   beijing  shanghai  guangzhou
a        0         1          2
c        3         4          5
d        6         7          8

In [106]: f2 = f.reindex(list('abcd'))

In [107]: f2
Out[107]:
   beijing  shanghai  guangzhou
a      0.0       1.0        2.0
b      NaN       NaN        NaN
c      3.0       4.0        5.0
d      6.0       7.0        8.0

In [112]: f3 = f.reindex(columns=['beijing','shanghai','xian','guangzhou'])

In [113]: f3
Out[113]:
   beijing  shanghai  xian  guangzhou
a        0         1   NaN          2
c        3         4   NaN          5
d        6         7   NaN          8

轴向上删除条目

通过drop方法,可以删除Series的一个元素,或者DataFrame的一行或一列。默认情况下,drop方法按行删除,且不会修改原数据,但指定axis=1则按列删除,指定inplace=True则修改原数据。

In [9]: s= pd.Series(np.arange(5),index=list('abcde'))

In [10]: s
Out[10]:
a    0
b    1
c    2
d    3
e    4
dtype: int32

In [11]: new_s = s.drop('c')

In [12]: new_s
Out[12]:
a    0
b    1
d    3
e    4
dtype: int32

In [13]: s
Out[13]:
a    0
b    1
c    2
d    3
e    4
dtype: int32

In [14]: df = pd.DataFrame(np.arange(16).reshape(4,4),columns=['one','two','three','four'])

In [15]: df
Out[15]:
   one  two  three  four
0    0    1      2     3
1    4    5      6     7
2    8    9     10    11
3   12   13     14    15

In [16]: df.drop(2)
Out[16]:
   one  two  three  four
0    0    1      2     3
1    4    5      6     7
3   12   13     14    15

In [17]: df.drop('two',axis = 1)  # 指定删除列,而不是默认的行
Out[17]:
   one  three  four
0    0      2     3
1    4      6     7
2    8     10    11
3   12     14    15

In [18]: df
Out[18]:
   one  two  three  four
0    0    1      2     3
1    4    5      6     7
2    8    9     10    11
3   12   13     14    15

In [21]: df.drop(2,inplace=True)  #修改原数据

In [22]: df
Out[22]:
   one  two  three  four
0    0    1      2     3
1    4    5      6     7
3   12   13     14    15

索引和切片

Series的打印效果,让我们感觉它像个二维表格,实际上它还是一维的,其索引和numpy的一维数组比较类似,但还是有点区别的。

In [23]: se = pd.Series(np.linspace(1,4,5),index=list('abcde'))

In [24]: se
Out[24]:
a    1.00
b    1.75
c    2.50
d    3.25
e    4.00
dtype: float64

In [28]: se['b']    # 利用我们专门指定的索引进行检索
Out[28]: 1.75

In [29]: se[2]  # 实际上默认还有一个从0开始的索引供我们使用
Out[29]: 2.5

In [30]: se[2:4]
Out[30]:
c    2.50
d    3.25
dtype: float64

In [31]: se[['b','a','d']]  # 根据索引顺序,值进行相应的排序,而不是我们认为的按原来的顺序
Out[31]:
b    1.75
a    1.00
d    3.25
dtype: float64

In [32]: se[[1,3]]  # 左闭右开 ,千万不要写成se[1,3]
Out[32]:
b    1.75
d    3.25
dtype: float64

In [33]: se[se>2]
Out[33]:
c    2.50
d    3.25
e    4.00
dtype: float64

In [35]: se['b':'c']  # 什么!居然是左闭右也闭!
Out[35]:
b    1.75
c    2.50
dtype: float64

In [36]: se['b':'c'] = 6  # 这样会修改原Series

In [37]: se
Out[37]:
a    1.00
b    6.00
c    6.00
d    3.25
e    4.00
dtype: float64

注意:如果你的Series是显式的整数索引,那么s[1]这样的取值操作会使用显式索引,而s[1:3]这样的切片操作却会使用隐式索引。Pandas开发人员在历史中为这种问题头疼不已,但没办法,现在还是这么混乱。

In [21]: s = pd.Series(['a','b','c'], index=[1,3,5])

In [22]: s
Out[22]:
1    a
3    b
5    c
dtype: object

In [23]: s[1]
Out[23]: 'a'

In [24]: s[1:3]
Out[24]:
3    b
5    c
dtype: object

对于DataFrame这种二维表格,情况有点不太一样,请务必注意!

核心思维:在DataFrame中,优先按列操作!

In [38]: df = pd.DataFrame(np.arange(16).reshape(4,4),
index=list('abcd'),columns=['one','two','three','four'])

In [39]: df
Out[39]:
   one  two  three  four
a    0    1      2     3
b    4    5      6     7
c    8    9     10    11
d   12   13     14    15

In [40]: df['two']  # 对于DataFrame默认是按列索引
Out[40]:
a     1
b     5
c     9
d    13
Name: two, dtype: int32

In [41]: df['b'] # KeyError,不能直接按行索引

In [43]: df[['one','four']]
Out[43]:
   one  four
a    0     3
b    4     7
c    8    11
d   12    15

In [44]: df[:2]  # 什么!切片的时候居然是按行进行!
Out[44]:
   one  two  three  four
a    0    1      2     3
b    4    5      6     7


In [46]: df['c':'d']  # 注意闭合区间
Out[46]:
   one  two  three  four
c    8    9     10    11
d   12   13     14    15

In [47]: df ['one':'three']  # 试图用列索引来切片,但明显后台按行索引去找了,没找到。
Out[47]:
Empty DataFrame
Columns: [one, two, three, four]
Index: []

In [48]: df < 5
Out[48]:
     one    two  three   four
a   True   True   True   True
b   True  False  False  False
c  False  False  False  False
d  False  False  False  False

In [49]: df[df<5] = 0   # 这一波操作和numpy很类似

In [50]: df
Out[50]:
   one  two  three  four
a    0    0      0     0
b    0    5      6     7
c    8    9     10    11
d   12   13     14    15

是不是觉得好难理解记忆?还是numpy那种索引方式更符合人的思维习惯?没关系,Pandas考虑到了这一点,提供了类似numpy的行+列的索引标签,也就是loc和iloc。这两者差一个字母i。后者是以隐含的整数索引值来索引的,前者则使用你指定的显式的索引来定位值。

In [50]: df
Out[50]:
   one  two  three  four
a    0    0      0     0
b    0    5      6     7
c    8    9     10    11
d   12   13     14    15

In [51]: df.loc['b', ['two','four']]  # 使用显式索引值,用逗号分隔行和列参数
Out[51]:
two     5
four    7
Name: b, dtype: int32

In [53]: df.loc['b':, 'two':'four']  # 切片方式,注意区间
Out[53]:
   two  three  four
b    5      6     7
c    9     10    11
d   13     14    15

In [54]: df.iloc[2, [3, 0, 1]]  # 用隐含的整数索引检索,但是这个打印格式好别扭
Out[54]:
four    11
one      8
two      9
Name: c, dtype: int32

In [55]: df.iloc[2]
Out[55]:
one       8
two       9
three    10
four     11
Name: c, dtype: int32

In [56]: df.iloc[1:,2:3] # 注意区间
Out[56]:
   three
b      6
c     10
d     14

In [57]: df.iloc[:,:3][df.three>5]  # 先切片,再布尔判断
Out[57]:
   one  two  three
b    0    5      6
c    8    9     10
d   12   13     14

提示:老版本的ix标签已经不提倡使用了。

算术和广播

Python运算符 Pandas方法
+ add()
- sub()、subtract()
* mul()、multiply() / truediv()、div()、divide()
// floordiv()
% mod()
** pow()

当对两个Series或者DataFrame对象进行算术运算的时候,返回的结果是两个对象的并集。如果存在某个索引不匹配时,将以缺失值NaN的方式体现,并对以后的操作产生影响。这类似数据库的外连接操作。

In [58]: s1 = pd.Series([4.2,2.6, 5.4, -1.9], index=list('acde'))

In [60]: s2 = pd.Series([-2.3, 1.2, 5.6, 7.2, 3.4], index= list('acefg'))

In [61]: s1
Out[61]:
a    4.2
c    2.6
d    5.4
e   -1.9
dtype: float64

In [62]: s2
Out[62]:
a   -2.3
c    1.2
e    5.6
f    7.2
g    3.4
dtype: float64

In [63]: s1+s2
Out[63]:
a    1.9
c    3.8
d    NaN
e    3.7
f    NaN
g    NaN
dtype: float64

In [64]: s1-s2
Out[64]:
a    6.5
c    1.4
d    NaN
e   -7.5
f    NaN
g    NaN
dtype: float64

In [65]: s1* s2
Out[65]:
a    -9.66
c     3.12
d      NaN
e   -10.64
f      NaN
g      NaN
dtype: float64

In [66]: df1 = pd.DataFrame(np.arange(9).reshape(3,3),columns=list('bcd'),index=['one','two','three'])

In [67]: df2 = pd.DataFrame(np.arange(12).reshape(4,3),columns=list('bde'),index=['two','three','five','six'])

In [68]: df1
Out[68]:
       b  c  d
one    0  1  2
two    3  4  5
three  6  7  8

In [69]: df2
Out[69]:
       b   d   e
two    0   1   2
three  3   4   5
five   6   7   8
six    9  10  11

In [70]: df1 + df2
Out[70]:
         b   c     d   e
five   NaN NaN   NaN NaN
one    NaN NaN   NaN NaN
six    NaN NaN   NaN NaN
three  9.0 NaN  12.0 NaN
two    3.0 NaN   6.0 NaN

其实,在上述过程中,为了防止NaN对后续的影响,很多时候我们要使用一些填充值:

In [71]: df1.add(df2, fill_value=0)
Out[71]:
         b    c     d     e
five   6.0  NaN   7.0   8.0
one    0.0  1.0   2.0   NaN
six    9.0  NaN  10.0  11.0
three  9.0  7.0  12.0   5.0
two    3.0  4.0   6.0   2.0

In [74]: df1.reindex(columns=df2.columns, fill_value=0) # 也可以这么干
Out[74]:
       b  d  e
one    0  2  0
two    3  5  0
three  6  8  0

注意,这里填充的意思是,如果某一方有值,另一方没有的话,将没有的那方的值填充为指定的参数值。而不是在最终结果中,将所有的NaN替换为填充值。

类似add的方法还有:

  • add:加法
  • sub:减法
  • div:除法
  • floordiv:整除
  • mul:乘法
  • pow:幂次方

DataFrame也可以和Series进行操作,这类似于numpy中不同维度数组间的操作,其中将使用广播机制。我们先看看numpy中的机制:

In [75]: a = np.arange(12).reshape(3,4)

In [76]: a
Out[76]:
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])

In [78]: a[0]   # 取a的第一行,这是一个一维数组
Out[78]: array([0, 1, 2, 3])

In [79]: a - a[0] # 二维数组减一维数组,在行方向上进行了广播
Out[79]:
array([[0, 0, 0, 0],
       [4, 4, 4, 4],
       [8, 8, 8, 8]])

DataFrame和Series之间的操作是类似的:

In [80]: df = pd.DataFrame(np.arange(12).reshape(4,3),columns=list('bde'),index=['one','two','three','four'])

In [81]: s = df.iloc[0]  # 取df的第一行生成一个Series

In [82]: df
Out[82]:
       b   d   e
one    0   1   2
two    3   4   5
three  6   7   8
four   9  10  11

In [83]: s
Out[83]:
b    0
d    1
e    2
Name: one, dtype: int32

In [84]: df - s # 减法会广播
Out[84]:
       b  d  e
one    0  0  0
two    3  3  3
three  6  6  6
four   9  9  9

In [85]: s2 = pd.Series(range(3), index=list('bef')) 

In [86]: df + s2  # 如果存在不匹配的列索引,则引入缺失值
Out[86]:
         b   d     e   f
one    0.0 NaN   3.0 NaN
two    3.0 NaN   6.0 NaN
three  6.0 NaN   9.0 NaN
four   9.0 NaN  12.0 NaN

In [87]: s3 = df['d'] # 取df的一列

In [88]: s3
Out[88]:
one       1
two       4
three     7
four     10
Name: d, dtype: int32

In [89]: df.sub(s3, axis='index')  # 指定按列进行广播
Out[89]:
       b  d  e
one   -1  0  1
two   -1  0  1
three -1  0  1
four  -1  0  1

在上面最后的例子中,我们通过axis='index'或者axis=0,在另外一个方向广播。

函数和映射

一些Numpy的通用函数对Pandas对象也有效:

In [91]: df = pd.DataFrame(np.random.randn(4,3), columns=list('bde'),index = ['one','two','three','four'])

In [92]: df
Out[92]:
              b         d         e
one   -0.522310  0.636599  0.992393
two    0.572624 -0.451550 -1.935332
three  0.021926  0.056706 -0.267661
four  -2.718122 -0.740140 -1.565448

In [93]: np.abs(df)
Out[93]:
              b         d         e
one    0.522310  0.636599  0.992393
two    0.572624  0.451550  1.935332
three  0.021926  0.056706  0.267661
four   2.718122  0.740140  1.565448

当然,你也可以自定义处理函数,然后使用pandas提供的apply方法,将它应用在每一列:

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

In [95]: df.apply(f)
Out[95]:
b    3.290745
d    1.376740
e    2.927725
dtype: float64

当然,可以指定按行应用f,只需要设置axis='columns'。也可以将引用函数的返回值设计为一个Series,这样最终结果会是个DataFrame:

In [96]: df.apply(f, axis='columns')
Out[96]:
one      1.514703
two      2.507956
three    0.324367
four     1.977981
dtype: float64

In [97]: def f2(x):
    ...:     return pd.Series([x.min(),x.max()], index=['min','max'])

In [98]: df.apply(f2)
Out[98]:
            b         d         e
min -2.718122 -0.740140 -1.935332
max  0.572624  0.636599  0.992393

还有更细粒度的apply方法,也就是DataFrame的applymap以及Series的map。它们逐一对每个元素进行操作,而不是整行整列的操作。请体会下面的例子:

In [99]: f3 = lambda x: '%.2f' % x

In [100]: df.applymap(f3)
Out[100]:
           b      d      e
one    -0.52   0.64   0.99
two     0.57  -0.45  -1.94
three   0.02   0.06  -0.27
four   -2.72  -0.74  -1.57

In [101]: df['d'].map(f3) # 获取d列,这是一个Series
Out[101]:
one       0.64
two      -0.45
three     0.06
four     -0.74
Name: d, dtype: object

排序和排名

排序分两种:根据索引排序和根据元素值排序

根据索引排序使用的是sort_index方法,它返回一个新的排好序的对象:

In [103]: s = pd.Series(range(4),index = list('dabc'))

In [104]: s
Out[104]:
d    0
a    1
b    2
c    3
dtype: int64

In [105]: s.sort_index() # 根据索引的字母序列排序
Out[105]:
a    1
b    2
c    3
d    0
dtype: int64

In [108]: df = pd.DataFrame(np.random.randint(10,size=(4,3)), columns=list('edb'),index = ['two','one','five','four'])

In [109]: df
Out[109]:
      e  d  b
two   7  6  1
one   5  6  8
five  8  4  1
four  7  0  3

In [110]: df.sort_index() # 默认按行索引排序,并以字母顺序
Out[110]:
      e  d  b
five  8  4  1
four  7  0  3
one   5  6  8
two   7  6  1

In [111]: df.sort_index(axis=1)  # 指定按列排序
Out[111]:
      b  d  e
two   1  6  7
one   8  6  5
five  1  4  8
four  3  0  7

In [112]: df.sort_index(axis=1,ascending=False)  # 默认升序,可以指定为倒序
Out[112]: 
      e  d  b
two   7  6  1
one   5  6  8
five  8  4  1
four  7  0  3

如果要根据某行或某列元素的值的大小进行排序,就要使用sort_values方法:

In [113]: s= pd.Series([4, 7,-3,2])

In [114]: s.sort_values()
Out[114]:
2   -3
3    2
0    4
1    7
dtype: int64


# np.nan缺失值会自动排到最后
In [115]: s2 = pd.Series([4, np.nan,7,np.nan,-3,2])

In [116]: s2
Out[116]:
0    4.0
1    NaN
2    7.0
3    NaN
4   -3.0
5    2.0
dtype: float64

In [117]: s2.sort_values()
Out[117]:
4   -3.0
5    2.0
0    4.0
2    7.0
1    NaN
3    NaN
dtype: float64

In [118]: df2 = pd.DataFrame({'b':[4,7,-3,2], 'a':[0,1,0,1]})

In [120]: df2
Out[120]:
   b  a
0  4  0
1  7  1
2 -3  0
3  2  1

In [121]: df2.sort_values(by='b')  # 根据某一列里的元素值进行排序
Out[121]:
   b  a
2 -3  0
3  2  1
0  4  0
1  7  1

In [122]: df2.sort_values(by=['a','b']) # 根据某些列进行排序
Out[122]:
   b  a
2 -3  0
0  4  0
3  2  1
1  7  1

除了排序,还有排名。Pandas的排名规则不太好理解,其规则如下:

  1. 以升序排名为例
  2. 所有数中最小的数排为1.0
  3. 按数大小依此类推,2.0、3.0、4.0给安排位次
  4. 如果有重复的数,则重复的排名相加除以重复的个数,得出一个排名
  5. 重复的数后面的排名,接着排

比如下面的例子:

In [123]: s = pd.Series([7,-5,7,4,2,0,4])

In [124]: s.rank()
Out[124]:
0    6.5
1    1.0
2    6.5
3    4.5
4    3.0
5    2.0
6    4.5
dtype: float64
  • -5最小,给排名1.0
  • 0其次,排2.0
  • 2再次,排3.0
  • 有2个4,于是4.0+5.0等于9,再除个数2,最终排名4.5。4.0和5.0两个排名并未使用。
  • 又有2个7,于是6.0+7.0等于13,再除2,最后排名6.5

也可以根据观察顺序进行排名位次分配,主要是重复的数,按顺序先后给排名,揣摩一下就能明白规律:

In [125]: s.rank(method='first')
Out[125]:
0    6.0
1    1.0
2    7.0
3    4.0
4    3.0
5    2.0
6    5.0
dtype: float64

还可以按最大值排名,并降序排列:

In [126]: s.rank(method='max',ascending=False)
Out[126]:
0    2.0
1    7.0
2    2.0
3    4.0
4    5.0
5    6.0
6    4.0
dtype: float64

DataFrame则可以根据行或列计算排名:

In [128]: df = pd.DataFrame(np.random.randint(-10,10,(4,3)),columns=list('abc'))

In [129]: df
Out[129]:
    a  b  c
0  -9 -1 -8
1 -10  8 -7
2  -4  2  6
3   9 -2 -7

In [130]: df.rank(axis='columns')
Out[130]:
     a    b    c
0  1.0  3.0  2.0
1  1.0  3.0  2.0
2  1.0  2.0  3.0
3  3.0  2.0  1.0

下面列出了method参数可以使用的排名方法:

  • average:默认方式,计算平均排名
  • min:最小排名
  • max:最大排名
  • first:观察顺序排名
  • dense:类似min,但组间排名总是增加1

统计和汇总

Pandas也有一套和Numpy类似的数学、统计学方法。不过在使用中要注意的是,Numpy通常将数组看作一个整体,而Pandas通常对列进行操作。当然,这两者也能单独对行进行操作。另外,Pandas内建了处理缺失值的功能,这一点是numpy不具备的。

In [131]: df = pd.DataFrame([[1.4, np.nan],[7.1,-4.2],[np.nan,np.nan],[0.75,-1.1]],index=list('abcd'),columns=['one','two'])

In [132]: df
Out[132]:
    one  two
a  1.40  NaN
b  7.10 -4.2
c   NaN  NaN
d  0.75 -1.1

In [133]: df.sum() # 默认对列进行求和,并返回一个Series对象,缺失值默认被忽略
Out[133]:
one    9.25
two   -5.30
dtype: float64

In [134]: df.sum(axis='columns') # 指定对行进行求和
Out[134]:
a    1.40
b    2.90
c    0.00
d   -0.35
dtype: float64

In [135]: df.mean(axis='columns', skipna=False) # 对行求平均值,但不忽略缺失值
Out[135]:
a      NaN
b    1.450
c      NaN
d   -0.175
dtype: float64

下面是主要的统计和汇总方法:

方法 描述
min 最小值
max 最大值
idxmin 返回某行或某列最小值的索引
idxmax 最大值的索引
cumsum 累加
cumprod 累乘
count 统计非NaN的个数
describe 汇总统计集合
quantile 计算样本的从0到1间的分位数
sum 求和
mean 平均值
median 中位数(50%)
mad 平均值的平均绝对偏差
prod 所有值的积
var 方差
std 标案差
skew 样本偏度,第三时刻值
kurt 样本峰度,第四时刻值
diff 计算第一个算术差值
pct_change 计算百分比
In [136]: df.idxmax()
Out[136]:
one    b
two    d
dtype: object

In [137]: df.idxmin()
Out[137]:
one    d
two    b
dtype: object

In [138]: df.cumsum()
Out[138]:
    one  two
a  1.40  NaN
b  8.50 -4.2
c   NaN  NaN
d  9.25 -5.3

In [139]: df.cumprod()
Out[139]:
     one   two
a  1.400   NaN
b  9.940 -4.20
c    NaN   NaN
d  7.455  4.62

In [144]: df.count()
Out[144]:
one    3
two    2
dtype: int64

最重要的describe方法:

In [140]: df.describe()
Out[140]:
            one       two
count  3.000000  2.000000
mean   3.083333 -2.650000
std    3.493685  2.192031
min    0.750000 -4.200000
25%    1.075000 -3.425000
50%    1.400000 -2.650000
75%    4.250000 -1.875000
max    7.100000 -1.100000

In [141]: s=pd.Series(list('aabc'*4))

In [143]: s.describe()  # 对于非数值型,统计类型不一样
Out[143]:
count     16
unique     3
top        a
freq       8
dtype: object

还有几个非常重要的方法:

  • unique:计算唯一值数组,并按观察顺序返回
  • value_counts:计数,并按多到少排序
  • isin:判断是否包含成员
In [147]: s = pd.Series(list('cadaabbcc'))

In [149]: uniques = s.unique() # 获取去重后的值

In [150]: uniques  # 这是一个array数组
Out[150]: array(['c', 'a', 'd', 'b'], dtype=object)

In [151]: s.value_counts() # 计数,默认从多到少排序
Out[151]:
a    3
c    3
b    2
d    1
dtype: int64

In [152]: pd.value_counts(s, sort=False) # 也可以这么调用,并且不排序
Out[152]:
d    1
c    3
a    3
b    2
dtype: int64

In [155]: mask = s.isin(['b','c'])  # 判断Series的元素在不在b和c里面

In [156]: mask # 这是一个bool类型
Out[156]:
0     True
1    False
2    False
3    False
4    False
5     True
6     True
7     True
8     True
dtype: bool

In [157]: s[mask] 
Out[157]:
0    c
5    b
6    b
7    c
8    c
dtype: object

文件读取

通过手动输入数据来生成Series和DataFrame对象。在实际工作中,这显然是不可能的。大多数时候,我们都要与外部进行数据交换,输入和输出。有很多的工具可以帮助我们读取和写入各种格式的数据,但是Pandas自己提供的读写工具更方便更适合。

数据交换主要有以下几种类型:

  • 读取文本文件或磁盘上的其它高效文件格式
  • 与数据库交互
  • 与网络资源,比如Web API进行交互

1、 文本格式数据的读取

在Pandas的使用场景中,最多的是将表格型的数据读取为DataFrame对象。实现这一功能的函数有很多,最常用的是read_csvread_table

下表列出了pandas主要的读写函数:

函数 说明
read_csv 读取默认以逗号作为分隔符的文件
read_table 读取默认以制表符分隔的文件
read_fwf 从特定宽度格式的文件中读取数据(无分隔符)
read_clipboard read_table的剪贴板版本
read_excel 从EXCEL的XLS或者XLSX文件中读取数据
read_hdf 读取用pandas存储的HDF5文件
read_html 从HTML文件中读取所有表格数据
read_json 从JSON字符串中读取数据
read_msgpack 读取MessagePack格式存储的任意对象
read_pickle 读取以Python Pickle格式存储的对象
read_sas 读取SAS系统中定制存储格式的数据集
read_sql 将SQL查询的结果读取出来
read_stata 读取stata格式的数据集
read_feather 读取Feather二进制格式

Pandas不但有这么多的读取函数,每个函数还有大量的可设置参数。比如read_csv到目前就有超过50个参数。这些函数和参数是如此的复杂,很难有人能够将它们全部记住,所以当你在读取文件时遇到了困难,请前往官方文档,那里的一些示例可能对你有帮助。

让我们来看看一些具体的例子,首先有这么一个文件:

# ex1.csv
a,b,c,d,message
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo

可以使用read_csv方法读取它,默认第一行为列索引,自动生成了行索引:

In [14]: df = pd.read_csv('d:/ex1.csv')

In [15]: df
Out[15]:
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

也可以使用read_table函数,并指定分隔符为逗号:

In [17]: pd.read_table('d:/ex1.csv',sep=',')
Out[17]:
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

对于不包含表头行的文件,比如下面的ex2,要么使用默认列名,要么我们指定列名:

1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo

注意指定列名用的参数名是names,不是columns:

In [18]: pd.read_csv('d:/ex2.csv',header=None) # 使用默认列名
Out[18]:
   0   1   2   3      4
0  1   2   3   4  hello
1  5   6   7   8  world
2  9  10  11  12    foo

In [19]: pd.read_csv('d:/ex2.csv', names=['a','b','c','d','message']) # 自定义
Out[19]:
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

如果你想将上面的message列当作DataFrame的行索引,可以将它传递给参数index_col:

# 注意其中的names依然必须包含message字符串,否则会弹出KeyError

In [20]: pd.read_csv('d:/ex2.csv', names=['a','b','c','d','message'],index_col='message')
Out[20]:
         a   b   c   d
message
hello    1   2   3   4
world    5   6   7   8
foo      9  10  11  12

如果想读取成分层索引,则需要为index_col参数传入一个包含列序号或列名的列表:

# csv_mindex.csv
key1,key2,value1,value2
one,a,1,2
one,b,3,4
one,c,5,6
one,d,7,8
two,a,9,10
two,b,11,12
two,c,13,14
two,d,15,16

分层索引类似多级索引,后面会介绍。

In [22]: df = pd.read_csv('d:/csv_mindex.csv', index_col=['key1','key2'])

In [23]: df
Out[23]:
           value1  value2
key1 key2
one  a          1       2
     b          3       4
     c          5       6
     d          7       8
two  a          9      10
     b         11      12
     c         13      14
     d         15      16

对于一些更复杂,或者说更不规整的文件,比如分隔符不固定的情况,需要有更多的处理技巧,比如下面这个文件,以不同数量的空格分隔:

# ex3.txt
            A         B         C
aaa -0.264438 -1.026059 -0.619500
bbb  0.927272  0.302904 -0.032399
ccc -0.264273 -0.386314 -0.217601
ddd -0.871858 -0.348382  1.100491

这时候需要传入一个正则表达式作为分隔符参数:

In [25]: result = pd.read_table('d:/ex3.txt', sep='\s+')

In [26]: result
Out[26]:
            A         B         C
aaa -0.264438 -1.026059 -0.619500
bbb  0.927272  0.302904 -0.032399
ccc -0.264273 -0.386314 -0.217601
ddd -0.871858 -0.348382  1.100491

上面的正则表达式是\s+。并且由于列名比数据的列数少一个,系统自动推断第一列应当作为行索引。

大多数读取函数都带有很多帮助我们处理各种异常情况的参数,比如可以使用skiprows来跳过数据中的指定行.

原始数据:

# hey!
a,b,c,d,message
# just wanted to make things more difficult for you
# who reads CSV files with computers, anyway?
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo

我们需要跳过无用的第1、3、4行废话:

In [27]: pd.read_csv('d:/ex4.csv', skiprows=[0,2,3])
Out[27]:
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

缺失值的处理是读取数据时非常重要的部分。通常情况下,缺失值要么不显示,直接空着,要么用一些标识值表示,比如NA或NULL。

看下面的数据,注意第三行的6和8之间有个空的:

something,a,b,c,d,message
one,1,2,3,4,NA
two,5,6,,8,world
three,9,10,11,12,foo

read_csv会将缺失值自动读成NaN:

In [30]: result = pd.read_csv('d:/ex5.csv')

In [31]: result
Out[31]:
  something  a   b     c   d message
0       one  1   2   3.0   4     NaN
1       two  5   6   NaN   8   world
2     three  9  10  11.0  12     foo

In [32]: pd.isnull(result)
Out[32]:
   something      a      b      c      d  message
0      False  False  False  False  False     True
1      False  False  False   True  False    False
2      False  False  False  False  False    False

我们可以额外指定na_values参数,将某些值也当作缺失值来看待,比如下面我们将3.0也当作缺失值对待:

In [40]: result = pd.read_csv('d:/ex5.csv',na_values=[3.0])

In [41]: result
Out[41]:
  something  a   b     c   d message
0       one  1   2   NaN   4     NaN
1       two  5   6   NaN   8   world
2     three  9  10  11.0  12     foo

甚至可以对不同的列,指定不同的缺失值标识:

In [42]: f = {'message':['foo','NA'],'something':['two']}

In [43]: result = pd.read_csv('d:/ex5.csv',na_values=f)

In [44]: result
Out[44]:
  something  a   b     c   d message
0       one  1   2   3.0   4     NaN
1       NaN  5   6   NaN   8   world
2     three  9  10  11.0  12     NaN

下表列出了read_csvread_table函数的一些常用参数:

参数 说明
path 文件路径
sep 指定分隔符或正则表达式
header 用作列名的行号
index_col 用作行索引的列名或列号
names 结果的列名列表
skiprows 从起始处,需要跳过的行
na_values 需要用NaN替换的值
comment 在行结尾处分隔注释的字符
parse_dates 尝试将数据解析为datetime,默认是False
keep_date_col 如果连接列到解析日期上,保留被连接的列,默认False
converters 包含列名称映射到函数的字典
dayfirst 解析非明确日期时,按国际格式处理
date_parser 用于解析日期的函数
nrows 从文件开头处读入的行数
iterator 返回一个TextParser对象,用于零散地读入文件
chunksize 用于迭代的块大小
skip_footer 忽略文件尾部的行数
verbose 打印各种解析器输出的信息
encoding 文本编码,比如UTF-8
thousands 定义千位分隔符,例如逗号或者圆点

2、分块读取

当我们处理大型文件的时候,读入文件的一个小片段或者按小块遍历文件是比较好的做法。

在这之前,我们最好先对Pandas的显示设置进行调整,使之更为紧凑:

In [45]: pd.options.display.max_rows = 10

这样,即使是大文件,最多也只会显式10行具体内容。

In [46]: result = pd.read_csv('d:/ex6.csv')

In [47]: result
Out[47]:
           one       two     three      four key
0     0.467976 -0.038649 -0.295344 -1.824726   L
1    -0.358893  1.404453  0.704965 -0.200638   B
2    -0.501840  0.659254 -0.421691 -0.057688   G
3     0.204886  1.074134  1.388361 -0.982404   R
4     0.354628 -0.133116  0.283763 -0.837063   Q
...        ...       ...       ...       ...  ..
9995  2.311896 -0.417070 -1.409599 -0.515821   L
9996 -0.479893 -0.650419  0.745152 -0.646038   E
9997  0.523331  0.787112  0.486066  1.093156   K
9998 -0.362559  0.598894 -1.843201  0.887292   G
9999 -0.096376 -1.012999 -0.657431 -0.573315   0

[10000 rows x 5 columns]

或者使用nrows参数,指明从文件开头往下只读n行:

In [48]: result = pd.read_csv('d:/ex6.csv',nrows=5)

In [49]: result
Out[49]:
        one       two     three      four key
0  0.467976 -0.038649 -0.295344 -1.824726   L
1 -0.358893  1.404453  0.704965 -0.200638   B
2 -0.501840  0.659254 -0.421691 -0.057688   G
3  0.204886  1.074134  1.388361 -0.982404   R
4  0.354628 -0.133116  0.283763 -0.837063   Q

或者指定chunksize作为每一块的行数,分块读入文件:

In [50]: chunker = pd.read_csv('d:/ex6.csv', chunksize=1000)

In [51]: chunker
Out[51]: <pandas.io.parsers.TextFileReader at 0x2417d6cfb38>

上面的TextFileReader对象是一个可迭代对象。例如我们可以遍历它,并对‘key’列进行聚合获得计数值:

In [52]: total = pd.Series([])

In [53]: for piece in chunker:
    ...:     total = total.add(piece['key'].value_counts(), fill_value=0)
    ...: total = total.sort_values(ascending=False)

In [54]: total
Out[54]:
E    368.0
X    364.0
L    346.0
O    343.0
Q    340.0
     ...
5    157.0
2    152.0
0    151.0
9    150.0
1    146.0
Length: 36, dtype: float64

In [55]: total[:10]
Out[55]:
E    368.0
X    364.0
L    346.0
O    343.0
Q    340.0
M    338.0
J    337.0
F    335.0
K    334.0
H    330.0
dtype: float64

3、写出数据

既然有读,必然有写。

可以使用DataFrame的to_csv方法,将数据导出为逗号分隔的文件:

In [57]: result
Out[57]:
        one       two     three      four key
0  0.467976 -0.038649 -0.295344 -1.824726   L
1 -0.358893  1.404453  0.704965 -0.200638   B
2 -0.501840  0.659254 -0.421691 -0.057688   G
3  0.204886  1.074134  1.388361 -0.982404   R
4  0.354628 -0.133116  0.283763 -0.837063   Q

In [58]: result.to_csv('d:/out.csv')

当然 ,也可以指定为其它分隔符,甚至将数据输出到sys.stdout中:

In [60]: result.to_csv(sys.stdout, sep='|')
|one|two|three|four|key
0|0.467976300189|-0.0386485396255|-0.295344251987|-1.82472622729|L
1|-0.358893469543|1.40445260007|0.704964644926|-0.20063830401500002|B
2|-0.50184039929|0.659253707223|-0.42169061931199997|-0.0576883018364|G
3|0.20488621220199998|1.07413396504|1.38836131252|-0.982404023494|R
4|0.354627914484|-0.13311585229599998|0.283762637978|-0.837062961653|Q

缺失值默认以空字符串出现,当然也可以指定其它标识值对缺失值进行标注,比如使用‘NULL’:

In [70]: data = pd.DataFrame(np.random.randint(9,size=9).reshape(3,3))

In [71]: data
Out[71]:
   0  1  2
0  7  7  3
1  8  1  5
2  2  4  2

In [72]: data.iloc[2,2] = np.nan

In [73]: data.to_csv(sys.stdout, na_rep='NULL')
,0,1,2
0,7,7,3.0
1,8,1,5.0
2,2,4,NULL

在写入的时候,我们还可以禁止将行索引和列索引写入:

In [74]: result.to_csv(sys.stdout, index=False, header=False)
0.467976300189,-0.0386485396255,-0.295344251987,-1.82472622729,L
-0.358893469543,1.40445260007,0.704964644926,-0.20063830401500002,B
-0.50184039929,0.659253707223,,-0.0576883018364,G
0.20488621220199998,1.07413396504,1.38836131252,-0.982404023494,R
0.354627914484,-0.13311585229599998,0.283762637978,-0.837062961653,Q

也可以挑选需要的列写入:

In [75]: result.to_csv(sys.stdout, index=False, columns=['one','three','key'])
one,three,key
0.467976300189,-0.295344251987,L
-0.358893469543,0.704964644926,B
-0.50184039929,,G
0.20488621220199998,1.38836131252,R
0.354627914484,0.283762637978,Q

Series的写入方式也是一样的:

In [76]: dates = pd.date_range('1/1/2019', periods=7) # 生成一个日期Series

In [77]: dates
Out[77]:
DatetimeIndex(['2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04',
               '2019-01-05', '2019-01-06', '2019-01-07'],
              dtype='datetime64[ns]', freq='D')

In [78]: ts = pd.Series(np.arange(7), index=dates) # 将日期作为索引

In [79]: ts
Out[79]:
2019-01-01    0
2019-01-02    1
2019-01-03    2
2019-01-04    3
2019-01-05    4
2019-01-06    5
2019-01-07    6
Freq: D, dtype: int32

In [80]: ts.to_csv('d:/tseries.csv') # 写入文件中

JSON和Pickle

假设有如下的JSON文件:

[{"a": 1, "b": 2, "c": 3},
 {"a": 4, "b": 5, "c": 6},
 {"a": 7, "b": 8, "c": 9}]

使用read_json函数可以自动将JSON数据集按照指定的顺序转换为Series或者DataFrame对象,其默认做法是假设JSON数据中的每个对象是表里的一行:

In [81]: data = pd.read_json('d:/example.json')

In [82]: data
Out[82]:
   a  b  c
0  1  2  3
1  4  5  6
2  7  8  9

反之,使用to_json函数,将pandas对象转换为json格式:

In [83]: print(data.to_json())
{"a":{"0":1,"1":4,"2":7},"b":{"0":2,"1":5,"2":8},"c":{"0":3,"1":6,"2":9}}

In [84]: print(data.to_json(orient='records')) # 与上面的格式不同
[{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}]

我们都知道,Python标准库pickle,可以支持二进制格式的文件读写,且高效方便。

pandas同样设计了用于pickle格式的读写函数read_pickleto_pickle

In [85]: df = pd.read_csv('d:/ex1.csv')

In [86]: df
Out[86]:
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

In [87]: df.to_pickle('d:/df_pickle')

In [88]: new_df = pd.read_pickle('d:/df_pickle')

In [89]: new_df
Out[89]:
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

HDF5

HDF5是一个备受好评的文件格式,广泛用于存储大量的科学计算数组。很多数据分析的实际工作中困难都在于I/O密集,而不是CPU密集,使用HDF5这样的工具可以大大加速你的应用。它以C库的形式提供,并且具有许多其它语言的接口,包括Java、MATLAB和Python。HDF5中的HDF代表分层数据格式。每个HDF5文件可以存储多个数据集并且支持元数据。支持多种压缩模式的即时压缩,使得重复模式的数据可以更高效地存储。HDF5适用于处理不适合在内存中存储地超大型数据,可以使你高效读写大型数据的一小块。

要注意的是HDF5并不是数据库,它是一种适合一次写入多次读取的数据集。尽管数据可以在任何时间添加到文件中,但如果多个写入者持续写入,文件可能会损坏。

在外部,可以通过使用PyTables或h5py等库直接访问HDF5文件,但Pandas提供了一个高阶的接口,可以简化存储过程。

Pandas使用HDFStore类来实现这一功能,类似字典一样的工作方式:

In [90]: df = pd.DataFrame({'a':np.random.randn(100)})  # 有100行

In [91]: df.head() # 看看前5行
Out[91]:
          a
0 -0.917062
1  0.797327
2  0.659787
3 -0.779638
4  0.550464

In [92]: store = pd.HDFStore('mydata.h5') # 生成HDF5文件

In [93]: store['obj1'] = df  # 以类似字典的方式,向文件里写入内容

In [94]: store['obj1_col'] = df['a'] # 再写一点

In [95]: store   # 看看信息,在当前工作目录下,你可以找到这个文件
Out[95]:
<class 'pandas.io.pytables.HDFStore'>
File path: mydata.h5

既然是类似字典的工作方式,那当然也可以像字典那样索引数据:

In [97]: store['obj1']
Out[97]:
           a
0  -0.917062
1   0.797327
2   0.659787
3  -0.779638
4   0.550464
..       ...
95 -2.042226
96  1.286631
97  0.487709
98 -0.202580
99  1.619085

[100 rows x 1 columns]

HDFStore支持两种工作模式,‘fixed’和‘table’。table的速度更慢,但支持一种类似数据库SQL语言的查询操作:

In [98]: store.put('obj2',df,format='table') # put是赋值的显式版本,允许我们设置其它选项

In [99]: store.select('obj2', where=['index >=10 and index <= 15'])  # 类似SQl语言的查询操作,要注意空格的位置
Out[99]:
           a
10 -1.430696
11 -0.616732
12 -0.643991
13 -0.004270
14  0.797136
15 -0.175095

In [100]: store.close() # 关闭文件

除此之外,Padas还提供了以上操作的快捷方式:

In [101]: df.to_hdf('mydata.h5','obj3', format='table')

In [102]: pd.read_hdf('mydata.h5', 'obj3' ,where=['index < 5'])
Out[102]:
          a
0 -0.917062
1  0.797327
2  0.659787
3 -0.779638
4  0.550464

EXCEL文件

Pandas支持Excle 2003或更高版本文件的读写。在内部,这需要使用附加包xlrd和openpyxl来分别读取XLS和XLSX文件。如果你的环境中没有这两个包,可能需要使用pip或者conda手动安装一下。

使用很简单,看下面的例子:

In [104]: xlsx = pd.ExcelFile('d:/ex1.xlsx') # 打开excel文件

In [105]: pd.read_excel(xlsx, 'Sheet1') # 读取指定的表
Out[105]:
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

或者使用更简洁的语法:

In [106]: df = pd.read_excel('d:/ex1.xlsx', 'Sheet1')

In [107]: df
Out[107]:
   a   b   c   d message
0  1   2   3   4   hello
1  5   6   7   8   world
2  9  10  11  12     foo

将pandas数据写回到excel文件中时,你必须先生成一个ExcelWriter,然后使用to_excel方法将数据写入:

In [108]: writer = pd.ExcelWriter('d:/ex2.xlsx') # 生成文件

In [109]: df.to_excel(writer, 'Sheet1') # 写入

In [110]: writer.save()  #关闭文件

当然,也可以使用快捷操作:

In [112]: df.to_excel('d:/ex3.xlsx')

Web交互

很多网站都有公开的API,通过JSON或者其它格式提供数据服务。我们可以利用Python的requests库来访问这些API。Anaconda环境中默认已经安装好了requests,如果你的环境中,没有安装这个包的话,可以通过pip或者conda进行安装:

pip install requests
conda install requests

下面我们以获取Github上最新的30条关于pandas的问题为例,介绍如何与Web API交互。

In [113]: import requests # 导入包

In [114]: url = 'https://api.github.com/repos/pandas-dev/pandas/issues' # 要访问的url

In [115]: response = requests.get(url) # 访问页面,需要等待一会

In [116]: response # 成功获取响应的数据
Out[116]: <Response [200]>

In [117]: data = response.json() # 解析为json格式

In [118]: data[0]['title'] # 查看第一个问题的标题
Out[118]: 'python-project-packaging-with-pandas'

data中的每个元素都是一个包含Github问题页面上的所有数据的字典(注释除外)。

In [119]: data[0]
Out[119]:
{'url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24779',
 'repository_url': 'https://api.github.com/repos/pandas-dev/pandas',
 'labels_url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24779/labels{/name}',
 'comments_url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24779/comments',
 'events_url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24779/events',
 'html_url': 'https://github.com/pandas-dev/pandas/issues/24779',
 'id': 399193081,
 'node_id': 'MDU6SXNzdWUzOTkxOTMwODE=',
 'number': 24779,
 'title': 'python-project-packaging-with-pandas',
 ......后面省略

我们可以将data直接传给DataFrame,并提取感兴趣的字段:

In [120]: issues = pd.DataFrame(data, columns= ['number','title','labels','state'])

In [121]: issues
Out[121]:
    number  ...  state
0    24779  ...   open
1    24778  ...   open
2    24776  ...   open
3    24775  ...   open
4    24774  ...   open
..     ...  ...    ...
25   24736  ...   open
26   24735  ...   open
27   24733  ...   open
28   24732  ...   open
29   24730  ...   open

上面我们截取了'number','title','labels','state'四个字段的内容,并构造了DataFrame对象,下面就可以利用pandas对它进行各种操作了!

数据库交互

在实际使用场景中,大部分数据并不是存储在文本或者Excel文件中的,而是一些基于SQL语言的关系型数据库中,比如MySQL。

从SQL中将数据读取为DataFrame对象是非常简单直接的,pandas提供了多个函数用于简化这个过程。

下面以Python内置的sqlite3标准库为例,介绍一下操作过程。

首先是生成数据库:

In [123]: import sqlite3  # 导入这个标准内置库

# 编写一条创建test表的sql语句
In [124]: query = """
     ...: CREATE TABLE test
     ...: (a VARCHAR(20), b VARCHAR(20), c REAL, d integer);""" 

In [125]: con  = sqlite3.connect('mydata.sqlite')  # 创建数据库文件,并连接

In [126]: con.execute(query) # 执行sql语句
Out[126]: <sqlite3.Cursor at 0x2417e5535e0>

In [127]: con.commit # 提交事务
Out[127]: <function Connection.commit>

再插入几行数据:

# 两个人和一只老鼠的信息
In [129]: data = [('tom', 'male',1.75, 20),
     ...:         ('mary','female',1.60, 18),
     ...:         ('jerry','rat', 0.2, 60)]
     ...:

# 再来一条空数据
In [130]: stmt = "INSERT INTO test VALUES(?,?,?,?)"

In [131]: con.executemany(stmt,data) # 执行多个语句
Out[131]: <sqlite3.Cursor at 0x2417e4b9f80>

In [132]: con.commit() # 再次提交事务

前面都是往数据库里写入内容,下面我们来读数据:

In [133]: cursor = con.execute('select * from test') # 执行查询语句

In [134]: rows = cursor.fetchall() # 获取查询结果

In [135]: rows
Out[135]:
[('tom', 'male', 1.75, 20),
 ('mary', 'female', 1.6, 18),
 ('jerry', 'rat', 0.2, 60)]

In [136]: cursor.description # 看看结果中的列名,其实就是数据表的列名
Out[136]:
(('a', None, None, None, None, None, None),
 ('b', None, None, None, None, None, None),
 ('c', None, None, None, None, None, None),
 ('d', None, None, None, None, None, None))

In [137]: pd.DataFrame(rows,columns= [x[0] for x in cursor.description])
Out[137]:
       a       b     c   d
0    tom    male  1.75  20
1   mary  female  1.60  18
2  jerry     rat  0.20  60

上面最后生成DataFrame时,使用了一个列表推导式来构成列名序列。

例子到这里大致就完成了,但是关于数据库的连接和查询操作实在是够繁琐的,你肯定不想每次都这么来一遍。那么怎么办呢?使用流行的Python的SQL工具包SQLAlchemy,它能简化你的数据库操作。同时,pandas提供了一个read_sql函数,允许你从通用的SQLAlchemy连接中轻松地读取数据。一个典型的操作如下:

In [138]: import sqlalchemy as sqla 

In [140]: db = sqla.create_engine('sqlite:///mydata.sqlite') # 创建连接

In [141]: pd.read_sql('select * from test', db) # 查询数据并转换为pandas对象
Out[141]:
       a       b     c   d
0    tom    male  1.75  20
1   mary  female  1.60  18
2  jerry     rat  0.20  60

在Anaconda中,已经默认安装了SQLAlchemy,可以直接使用。如果你的环境中没有SQLAlchemy,请自定安装,或者搜索教程进行学习。

处理缺失值

在进行数据分析和建模的过程中,我们80%的时间往往花在数据准备上:加载、清理、转换、处理和重新排列。为了提高这一过程的效率,Pandas提供了一系列的高级、灵活和快速的工具集,配合Python语言内置的处理功能,可以满足绝大多数场景下的使用需求。

Pandas中,使用numpy.nan标识缺失值,在打印的时候,经常以空字符串、NA、NaN、NULL等形式出现。Python内置的None值也被当作缺失值处理。但合法的缺失值只有NaN和None,另外一些我们自己定义的缺失值不属于语法上的,比如你将-999看作缺失值。

在Pandas中, None和NaN的区别:

  • None被看作一个object对象,需要消耗更多的资源,处理速度更慢。不支持一些数学操作,因为None+数字是错误的语法。很多时候None会自动转换成NaN。
  • NaN是float64类型,虽然名字叫做‘不是一个数’,但却属于数字类,可以进行数学运算不会报错,虽然所有和它进行计算的最终结果依然是NaN。它的运算速度更快,还支持全局性的操作。

以下是主要的缺失值处理方法:

  • dropna:删除缺失值
  • fillna: 用某些值填充缺失的数据或使用插值方法(比如ffill\bfill)
  • isnull:判断哪些值是缺失值,返回布尔
  • notnull:isnull的反函数

1、发现缺失值

Pandas 数据结构有两种有效的方法可以发现缺失值:isnull() 和 notnull()。每种方法都 返回布尔类型的掩码数据,例如:

In[13]: data = pd.Series([1, np.nan, 'hello', None]) 

In[14]: data.isnull() 
Out[14]: 0    False
		 1     True        
         2    False       
         3     True    
         dtype: bool

布尔类型掩码数组可以直接作为 Series 或 DataFrame 的索引使用:

In[15]: data[data.notnull()] 
Out[15]: 0        1         
		 2    hello          
		 dtype: object

在 Series 里使用的 isnull() 和 notnull() 同样适用于 DataFrame,产生的结果同样是布尔类型。

2、删除缺失值

通常使用dropna方法滤删除缺失值,默认它不直接修改数据,而是返回一个新对象。如果想原地修改,举一反三,请尝试inplace参数:

In [12]: from numpy import nan as NA  # 导入惯例

In [13]: s = pd.Series([1, NA, 3.5, NA, 7])

In [14]: s
Out[14]:
0    1.0
1    NaN
2    3.5
3    NaN
4    7.0
dtype: float64

In [15]: s.dropna()  # 本质上就是把缺失值删除
Out[15]:
0    1.0
2    3.5
4    7.0
dtype: float64

In [16]: s[s.notnull()]  # 等同于上面的操作
Out[16]:
0    1.0
2    3.5
4    7.0
dtype: float64

在处理DataFrame对象的缺失值的时候,可能会复杂点。无法删除df的单个元素,只能整行整列的删除,dropna默认情况下会删除包含缺失值的行!为什么呢?因为DataFrame对象的每一行数据,在实际中,相当于一个样本,大量的样本中删除了一条,关系不大。但DataFrame对象的每一列相当于大量样本中共同的某个特征的值,如果删除了一个特征,那么对整个样本集的影响非常大。

In [20]: df = pd.DataFrame([[1, 6.5, 3],[1, NA, NA],[NA, NA, NA],[NA, 6.5,3]])

In [21]: df
Out[21]:
     0    1    2
0  1.0  6.5  3.0
1  1.0  NaN  NaN
2  NaN  NaN  NaN
3  NaN  6.5  3.0

In [22]: df.dropna() # 只剩1行了
Out[22]:
     0    1    2
0  1.0  6.5  3.0

In [23]: df.dropna(how='all') # 只将整行都是缺失值的删除
Out[23]:
     0    1    2
0  1.0  6.5  3.0
1  1.0  NaN  NaN
3  NaN  6.5  3.0

In [24]: df[4] = NA  # 新增一列全是缺失值

In [25]: df
Out[25]:
     0    1    2   4
0  1.0  6.5  3.0 NaN
1  1.0  NaN  NaN NaN
2  NaN  NaN  NaN NaN
3  NaN  6.5  3.0 NaN

In [26]: df.dropna(axis=1, how='all') # 指定以列的形式删除
Out[26]:
     0    1    2
0  1.0  6.5  3.0
1  1.0  NaN  NaN
2  NaN  NaN  NaN
3  NaN  6.5  3.0

注意上面axis和how参数的用法。

还可以通过thresh参数设置行或列中非缺失值的最小数量,在此数量以下的将被删除。

3、补全缺失值

所有数据都是宝贵的,大多数时候,我们不希望丢弃原始数据,而是补全缺失值。

fillna是补全缺失值的方法,为它提供一个固定值即可:

In [31]: df = pd.DataFrame(np.random.randn(7,3))

In [32]: df
Out[32]:
          0         1         2
0 -0.229682 -0.483246 -0.063835
1  0.716649  1.593639 -1.364550
2 -1.362614  1.628310 -1.617992
3  1.128828 -1.120265 -0.657313
4  1.078143  1.136835 -0.427125
5  0.441696  0.219477  0.695700
6 -0.501183  1.453678 -2.734985

In [33]: df.iloc[:4, 1] = NA

In [35]: df.iloc[:2, 2] = NA

In [36]: df
Out[36]:
          0         1         2
0 -0.229682       NaN       NaN
1  0.716649       NaN       NaN
2 -1.362614       NaN -1.617992
3  1.128828       NaN -0.657313
4  1.078143  1.136835 -0.427125
5  0.441696  0.219477  0.695700
6 -0.501183  1.453678 -2.734985

In [37]: df.fillna(0)
Out[37]:
          0         1         2
0 -0.229682  0.000000  0.000000
1  0.716649  0.000000  0.000000
2 -1.362614  0.000000 -1.617992
3  1.128828  0.000000 -0.657313
4  1.078143  1.136835 -0.427125
5  0.441696  0.219477  0.695700
6 -0.501183  1.453678 -2.734985

也可以提供一个字典,为不同的列设定不同的填充值。

In [38]: df.fillna({1:1, 2:2})
Out[38]:
          0         1         2
0 -0.229682  1.000000  2.000000
1  0.716649  1.000000  2.000000
2 -1.362614  1.000000 -1.617992
3  1.128828  1.000000 -0.657313
4  1.078143  1.136835 -0.427125
5  0.441696  0.219477  0.695700
6 -0.501183  1.453678 -2.734985

当然,fillna也不会原地修改数据,如果你想,请使用inplace参数:

In [39]: _ = df.fillna(0, inplace=True)

In [40]: df
Out[40]:
          0         1         2
0 -0.229682  0.000000  0.000000
1  0.716649  0.000000  0.000000
2 -1.362614  0.000000 -1.617992
3  1.128828  0.000000 -0.657313
4  1.078143  1.136835 -0.427125
5  0.441696  0.219477  0.695700
6 -0.501183  1.453678 -2.734985

也可以使用ffill和bfill这种插值法填充缺失值:

In [41]: df = pd.DataFrame(np.random.randn(6,3))

In [42]: df.iloc[2:, 1]=NA

In [43]: df.iloc[4:, 2]=NA

In [44]: df
Out[44]:
          0         1         2
0 -0.858762  0.083342 -0.315598
1 -0.211846  0.076648  1.188298
2 -0.513364       NaN  0.079216
3  0.398399       NaN -0.290225
4 -1.375898       NaN       NaN
5  0.932812       NaN       NaN

In [45]: df.fillna(method='ffill') # 使用前一个值进行填充
Out[45]:
          0         1         2
0 -0.858762  0.083342 -0.315598
1 -0.211846  0.076648  1.188298
2 -0.513364  0.076648  0.079216
3  0.398399  0.076648 -0.290225
4 -1.375898  0.076648 -0.290225
5  0.932812  0.076648 -0.290225

In [46]: df.fillna(method='ffill',limit=2)  # 限制填充次数
Out[46]:
          0         1         2
0 -0.858762  0.083342 -0.315598
1 -0.211846  0.076648  1.188298
2 -0.513364  0.076648  0.079216
3  0.398399  0.076648 -0.290225
4 -1.375898       NaN -0.290225
5  0.932812       NaN -0.290225

In [47]: df.fillna(method='bfill')  # 后向填充此时无效
Out[47]:
          0         1         2
0 -0.858762  0.083342 -0.315598
1 -0.211846  0.076648  1.188298
2 -0.513364       NaN  0.079216
3  0.398399       NaN -0.290225
4 -1.375898       NaN       NaN
5  0.932812       NaN       NaN

其实使用fillna有很多技巧,需要大家平时多收集多尝试,比如使用平均值来填充:

In [48]: s = pd.Series([1, NA, 3.5, NA, 7])

In [49]: s.fillna(s.mean())
Out[49]:
0    1.000000
1    3.833333
2    3.500000
3    3.833333
4    7.000000
dtype: float64

删除重复值

原始数据中,往往包含大量重复的行,需要我们删除,让数据集更健康。

In [51]: df = pd.DataFrame({'k1':['one','two']*3 + ['two'], 'k2':[1,1,2,3,3,4,4]})

In [52]: df  # 最后一行是重复的
Out[52]:
    k1  k2
0  one   1
1  two   1
2  one   2
3  two   3
4  one   3
5  two   4
6  two   4

In [53]: df.duplicated()  # 判断是否是重复行
Out[53]:
0    False
1    False
2    False
3    False
4    False
5    False
6     True
dtype: bool

In [54]: df.drop_duplicates() # 删除重复行
Out[54]:
    k1  k2
0  one   1
1  two   1
2  one   2
3  two   3
4  one   3
5  two   4

In [55]: df # 并没有改变原数据
Out[55]:
    k1  k2
0  one   1
1  two   1
2  one   2
3  two   3
4  one   3
5  two   4
6  two   4

上面,使用duplicated方法判断各行是否有重复,并返回一个布尔值Series。然后使用drop_duplicates方法将重复行删除,留下那些不重复的。

如果想指定根据某列的数据进行去重判断和操作,可以指定列名:

In [56]: df['v1'] = range(7)

In [57]: df
Out[57]:
    k1  k2  v1
0  one   1   0
1  two   1   1
2  one   2   2
3  two   3   3
4  one   3   4
5  two   4   5
6  two   4   6

In [58]: df.drop_duplicates(['k1'])
Out[58]:
    k1  k2  v1
0  one   1   0
1  two   1   1

默认情况下都是保留第一个观察到的值,如果想保留最后一个,可以使用参数keep='last':

In [59]: df.drop_duplicates(['k1','k2'], keep='last')
Out[59]:
    k1  k2  v1
0  one   1   0
1  two   1   1
2  one   2   2
3  two   3   3
4  one   3   4
6  two   4   6

替换

可以使用replace将pandas对象中的指定值替换为别的值:

In [77]: df = pd.DataFrame(np.random.randint(12,size=(4,3)))

In [78]: df
Out[78]:
   0  1   2
0  4  5  10
1  3  0   3
2  7  7   4
3  6  4   4

In [79]: df.replace(4, NA)  # 将4替换为缺失值
Out[79]:
     0    1     2
0  NaN  5.0  10.0
1  3.0  0.0   3.0
2  7.0  7.0   NaN
3  6.0  NaN   NaN

In [80]: df.replace([3,4], NA) # 将3和4都替换为缺失值
Out[80]:
     0    1     2
0  NaN  5.0  10.0
1  NaN  0.0   NaN
2  7.0  7.0   NaN
3  6.0  NaN   NaN

In [81]: df.replace([3,4], [NA,0]) # 3和4分别替换为缺失值和0
Out[81]:
     0    1     2
0  0.0  5.0  10.0
1  NaN  0.0   NaN
2  7.0  7.0   0.0
3  6.0  0.0   0.0

In [82]: df.replace({3:NA,4:0})  # 参数的字典形式
Out[82]:
     0    1     2
0  0.0  5.0  10.0
1  NaN  0.0   NaN
2  7.0  7.0   0.0
3  6.0  0.0   0.0

重命名轴索引

与Series对象类似,轴索引也有一个map方法:

In [83]: df = pd.DataFrame(np.arange(12).reshape((3, 4)),
    ...:                     index=['Ohio', 'Colorado', 'New York'],
    ...:                     columns=['one', 'two', 'three', 'four'])
    ...:

In [84]: transform = lambda x: x[:4].upper() # 截取前4个字符并大写

In [85]: df.index.map(transform) # map的结果
Out[85]: Index(['OHIO', 'COLO', 'NEW '], dtype='object')

In [86]: df.index = df.index.map(transform) #用结果修改原来的index

In [87]: df
Out[87]:
      one  two  three  four
OHIO    0    1      2     3
COLO    4    5      6     7
NEW     8    9     10    11

还可以使用rename方法修改索引,且不修改原数据:

# 参数的值是对索引进行修改的处理函数,比如str.title
In [88]: df.rename(index=str.title, columns=str.upper)
Out[88]:
      ONE  TWO  THREE  FOUR
Ohio    0    1      2     3
Colo    4    5      6     7
New     8    9     10    11

In [89]: df # 原值未变
Out[89]:
      one  two  three  four
OHIO    0    1      2     3
COLO    4    5      6     7
NEW     8    9     10    11

或者使用字典的方式,将指定的索引重命名为新值:

In [90]: df.rename(index={'OHIO': 'INDIANA'},
    ...:             columns={'three': 'peekaboo'})
    ...:
Out[90]:
         one  two  peekaboo  four
INDIANA    0    1         2     3
COLO       4    5         6     7
NEW        8    9        10    11

In [91]: df
Out[91]:
      one  two  three  four
OHIO    0    1      2     3
COLO    4    5      6     7
NEW     8    9     10    11

使用inplace=True可以原地修改数据集。

离散化和分箱

离散化,就是将连续值转换为一个个区间内,形成一个个分隔的‘箱子’。假设我们有下面的一群人的年龄数据,想将它们进行分组,并放入离散的年龄箱内:

ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]

我们预先定义1825、2635、36~60以及61及以上等若干组。

Pandas提供一个cut方法,帮助我们实现分箱功能:

In [93]: bins = [18,25,35,60,100]

In [94]: cats = pd.cut(ages,bins)

In [95]: cats
Out[95]:
[(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]]

返回的cats是一个特殊的Categorical对象,输出描述了12个年龄值分别处于哪个箱子中。cats包含一系列的属性:

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

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

In [98]: cats.describe
Out[98]:
<bound method Categorical.describe of [(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]]>

In [99]: pd.value_counts(cats)  # 各个箱子的数量
Out[99]: 
(18, 25]     5
(35, 60]     3
(25, 35]     3
(60, 100]    1
dtype: int64

分箱的区间通常是左开右闭的,如果想变成左闭右开,请设置参数right=False。

可以定义labels参数,来自定义每种箱子的名称:

In [100]: group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']^M
     ...: pd.cut(ages, bins, labels=group_names)
     ...:
     ...:
Out[100]:
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior]

如果你不提供分箱的区间定义,而是直接要求分隔成整数个等分区间,可以这么做:

In [101]: d =np.random.rand(20)

In [102]: d
Out[102]:
array([0.83732945, 0.0850416 , 0.66540597, 0.90479238, 0.99222014,
       0.39409122, 0.91896172, 0.87163655, 0.31374598, 0.27726111,
       0.7716572 , 0.79131961, 0.42805445, 0.29934685, 0.19077374,
       0.79701771, 0.93789892, 0.93536338, 0.32299602, 0.305671  ])

In [103]: pd.cut(d, 4, precision=2) # 精度限制在两位
Out[103]:
[(0.77, 0.99], (0.084, 0.31], (0.54, 0.77], (0.77, 0.99], (0.77, 0.99], ..., (0.77, 0.99], (0.77, 0.99], (0.77, 0.99], (0.31, 0.54], (0.084, 0.31]]
Length: 20
Categories (4, interval[float64]): [(0.084, 0.31] < (0.31, 0.54] < (0.54, 0.77] < (0.77, 0.99]]

cut函数执行的时候,分箱区间要么是你指定的,要么是均匀大小的。还有一种分箱方法叫做qcut,它是使用样本的分位数来分割的,而不是样本值的大小。比如下面的操作,将使每个箱子中元素的个数相等:

In [104]: data = np.random.randn(1000)

In [105]: cats = pd.qcut(data,4)

In [106]: cats
Out[106]:
[(0.644, 2.83], (-0.0344, 0.644], (-0.0344, 0.644], (-0.734, -0.0344], (-0.734, -0.0344], ..., (-3.327, -0.734], (-0.734, -0.0344], (0.644, 2.83], (-0.734, -0.0344], (-0.0344, 0.644]]
Length: 1000
Categories (4, interval[float64]): [(-3.327, -0.734] < (-0.734, -0.0344] < (-0.0344, 0.644] < (0.644, 2.83]]

In [108]: pd.value_counts(cats) # 各箱子中的元素个数相同
Out[108]:
(0.644, 2.83]        250
(-0.0344, 0.644]     250
(-0.734, -0.0344]    250
(-3.327, -0.734]     250
dtype: int64

qcut还可以自定义0~1之间的分位数:

pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.])

离散化函数对于分位数和分组分析特别有用。

检测和过滤

检测和过滤异常值是数据清洗过程中非常重要的一步。比如你手里有个一万成人的身高数据,其中有几个3m以上,1m以下的数据,这都是属于异常值,需要被过滤掉。

我们来看下面的一组正态分布的数据:

In [109]: df = pd.DataFrame(np.random.randn(1000, 4))

In [110]: df.describe()
Out[110]:
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.001764     0.023018     0.038956     0.016735
std       0.998835     1.041113     1.025342     1.019077
min      -3.035805    -3.056706    -2.857154    -2.922755
25%      -0.654426    -0.695994    -0.712627    -0.702766
50%      -0.005015     0.011862     0.020562    -0.041231
75%       0.660182     0.683478     0.732590     0.758038
max       3.323073     3.701470     3.280375     3.997876

如果你想找出第二列数据中绝对值大于3的元素:

In [113]: col = df[2]

In [114]: col[np.abs(col) > 3]
Out[114]:
30     3.091161
113    3.280375
Name: 2, dtype: float64

如果要选出所有行内有值大于3或小于-3的行,可以使用下面的any方法搭配:

In [125]: df[(np.abs(df)>3).any(1)]
Out[125]:
            0         1         2         3
28   0.008674  0.046048 -0.171580  3.997876
30   0.709758 -1.871982  3.091161 -0.819429
113  0.432223 -0.675313  3.280375  0.841355
169  3.323073 -0.608988  0.685795 -0.710693
177 -1.514524 -3.056706 -0.760937  1.300434
322  3.296765  0.971996  0.114804  1.855576
410  3.246140 -0.039501  1.530122  1.502243
496 -3.035805 -0.535662  0.703911  0.916483
575 -0.127245  3.701470 -0.642512  0.281001
720  3.045646  1.266809  1.263198  1.429049
799  0.523183 -0.246954  1.132868  3.141117

上面的代码怎么理解呢?首先,我们对整个df取绝对值,然后和3比较,形成一个bool的DataFrame,再使用any在行方向(参数1的作用)进行判断是否有True的存在,如果有,则保存在一个Series中,最后,用这个Series作为行号取df取出对应的行。

我们还可以将绝对值大于3的数分别设置为+3和-3,只需要使用np.sign(x)函数,这个函数根据x的符号分别生成+1和-1:

In [127]: df[np.abs(df) > 3] = np.sign(df) * 3
In [130]: df.describe()
Out[130]:
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean     -0.002640     0.022374     0.038584     0.015596
std       0.995851     1.038699     1.024224     1.015232
min      -3.000000    -3.000000    -2.857154    -2.922755
25%      -0.654426    -0.695994    -0.712627    -0.702766
50%      -0.005015     0.011862     0.020562    -0.041231
75%       0.660182     0.683478     0.732590     0.758038
max       3.000000     3.000000     3.000000     3.000000

In [133]: (np.sign(df)*3).head()
Out[133]:
     0    1    2    3
0  3.0  3.0 -3.0 -3.0
1  3.0  3.0 -3.0 -3.0
2 -3.0 -3.0 -3.0 -3.0
3 -3.0 -3.0  3.0 -3.0
4  3.0 -3.0  3.0 -3.0

随机和抽样

有时候,我们需要打乱原有的数据顺序,让数据看起来像现实中比较混沌、自然的样子。这里推荐一个permutation操作,它来自numpy.random,可以随机生成一个序列:

In [134]: order = np.random.permutation(5) # 5个数

In [135]: order
Out[135]: array([3, 4, 1, 2, 0])

然后我们用它处理下面的df,让行的顺序变成和order的一样:

In [136]: df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))

In [137]: df
Out[137]:
    0   1   2   3
0   0   1   2   3
1   4   5   6   7
2   8   9  10  11
3  12  13  14  15
4  16  17  18  19

In [138]: df.take(order)  #
Out[138]:
    0   1   2   3
3  12  13  14  15
4  16  17  18  19
1   4   5   6   7
2   8   9  10  11
0   0   1   2   3

In [139]: df.iloc[order]  # 同上
Out[139]:
    0   1   2   3
3  12  13  14  15
4  16  17  18  19
1   4   5   6   7
2   8   9  10  11
0   0   1   2   3

可以看到,通过take函数,使用order作为参数,打乱了df的行顺序。

还有一种叫做抽样的操作,从原样本集合中抽取一部分形成新的样本集合,分重复抽样和不重复抽样。pandas提供的sample函数帮我们实现了这一功能:

In [140]: df.sample(n=3)
Out[140]:
    0   1   2   3
0   0   1   2   3
4  16  17  18  19
1   4   5   6   7

In [141]: df.sample(n=10)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)

In [142]: df.sample(n=10,replace=True)
Out[142]:
    0   1   2   3
3  12  13  14  15
2   8   9  10  11
2   8   9  10  11
0   0   1   2   3
3  12  13  14  15
0   0   1   2   3
2   8   9  10  11
4  16  17  18  19
1   4   5   6   7
3  12  13  14  15
  • n=3:指定从原来数据中抽取3行
  • n=10:弹出异常,因为原数据不够10行
  • replace=True:可以重复抽取,这样10行可以,因为有重复

很明显,取样的操作是针对每行的。前面我们说过,一行就是一条记录,一个样本。

字符串操作

Python内置的字符串操作和re正则模块可以帮我们解决很多场景下的字符串操作需求。但是在数据分析过程中,它们有时候比较尴尬,比如:

In [143]: dic= {'one':'feixue', 'two':np.nan, 'three':'tom', 'five':'jerry@film'}

In [144]: s = pd.Series(dic)

In [145]: s
Out[145]:
one          feixue
two             NaN
three           tom
five     jerry@film
dtype: object

我现在想将s中的字母都大写,通过Python内置字符串方法,你可能会这么设计:

In [159]: s.map(lambda x : x.upper())
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)

但是,弹出了异常,原因是数据有一个缺失值NaN,这个值不是字符串,没有upper方法。

那怎么办呢?Pandas为这一类整体性的操作,提供了专门的字符串函数,帮助我们跳过缺失值等异常情况,对能够进行操作的每个元素进行处理:

In [160]: s.str.upper()
Out[160]:
one          FEIXUE
two             NaN
three           TOM
five     JERRY@FILM
dtype: object

这就是Series的str属性,在它的基础上甚至可以使用正则表达式的函数。

下面是部分可用的Series.str的字符串操作方法,名字基本和Python字符串内置方法相同:

  • cat :粘合字符串
  • contains:是否包含的判断
  • count:计数
  • extract:返回匹配的字符串组
  • endswith:以xx结尾判断
  • startswith:以xx开始判断
  • findall:查找
  • get:获取
  • isalnum:类型判断
  • isalpha:类型判断
  • isdecimal:类型判断
  • isdigit:类型判断
  • islower:是否小写
  • isnumeric:类型判断
  • isupper:是否大写
  • join:连接
  • len:长度
  • lower:小写
  • upper:大写
  • match:匹配
  • pad:将空白加到字符串的左边、右边或者两边
  • center:居中
  • repeat:重复
  • replace:替换
  • slice:切片
  • split:分割
  • strip:脱除
  • lstrip:左脱除
  • rstrip:右脱除

最后,思考一下,DataFrame怎么处理str呢?

分层索引

Pandas提供了Panel和Panel4D对象解决三维和四维数据的处理需求,但更常用的还是分层索引。分层索引是Pandas的重要特性,允许我们在一个轴向上拥有多个索引层级,它提供了一种在更低维度的形式中处理更高维度数据的方式。也就是如何用Series、DataFrame处理三维、四维等等高维度的数据。

这里还有一些 Pandas 的基本数据结构没有介绍到,包括 pd.Panel 对象和 pd.Panel4D 对象。这两种数据结构可以分别看成是(一维数组)Series 和(二维数组)DataFrame 的三维与四维形式。如果你熟悉Series 和 DataFrame 的使用方法,那么Panel 和 Panel4D 使用起来也会很简单,ix、loc 和 iloc 索引器在高维数据结构上的用法更是完全相同。
但是本书并不打算进一步介绍这两种数据结构,我个人认为多级索引在大多数情况下 都是更实用、更直观的高维数据形式。另外,Panel 采用密集数据存储形式,而多级 索引采用稀疏数据存储形式。在解决许多真实的数据集时,随着维度的不断增加,密 集数据存储形式的效率将越来越低。但是这类数据结构对一些有特殊需求的应用还是有用的。

比如有下面的数据:

In [168]: s = pd.Series(np.random.randn(9), index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'], [1, 2, 3, 1, 3, 1, 2, 2, 3]])

In [169]: s
Out[169]:
a  1    0.283490
   2    0.295529
   3    0.277676
b  1    0.487573
   3    0.091161
c  1    0.285157
   2   -0.806851
d  2   -0.287969
   3   -0.696511
dtype: float64

In [170]: s.index
Out[170]:
MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],
           labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])

MultiIndex就是一个分层索引对象,在打印的时候会进行规整的美化。

下面看下一些基本的操作:

In [171]: s['b']
Out[171]:
1    0.487573
3    0.091161
dtype: float64

In [172]: s['b':'c']
Out[172]:
b  1    0.487573
   3    0.091161
c  1    0.285157
   2   -0.806851
dtype: float64

In [173]: s.loc[['b','d']]
Out[173]:
b  1    0.487573
   3    0.091161
d  2   -0.287969
   3   -0.696511
dtype: float64

In [174]: s.loc['b','d'] # 这样不可以
---------------------------------------------------------------------------
IndexingError                             Traceback (most recent call last)
IndexingError: Too many indexers

In [175]: s.loc['b',1] # 但是这样可以
Out[175]: 0.48757273896298425

In [176]: s.loc[:, 2] # 或者这样
Out[176]:
a    0.295529
c   -0.806851
d   -0.287969
dtype: float64

tup = []

我们还可以这样来生成MultiIndex,请体会它的不同之处:

In [3]: tup = [('beijing',2000),('beijing',2019),
   ...:         ('shanghai',2000),('shanghai',2019),
   ...:         ('guangzhou',2000),('guangzhou',2019)]

In [4]: values = [10000,100000,6000,60000,4000,40000]

In [7]: index = pd.MultiIndex.from_tuples(tup) # 利用元组生成MultiIndex

In [8]: sss = pd.Series(values, index=index) # 提供一个MultiIndex作为索引

In [9]: sss
Out[9]:
beijing    2000     10000
           2019    100000
shanghai   2000      6000
           2019     60000
guangzhou  2000      4000
           2019     40000
dtype: int64

更多的创建MultiIndex的方法还有:

  • 从列表:pd.MultiIndex.from_arrays([['a','a','b','b'],[1,2,1,2]])
  • 从元组:pd.MultiIndex.from_tuples([('a',1),('a',2),('b',1),('b',2)])
  • 笛卡儿积:pd.MultiIndex.from_product([['a','b'],[1,2]])
  • 直接构造:pd.MultiIndex(levels=[['a','b'],[1,2]],labels=[[0,0,1,1],[0,1,0,1]])

生成MultiIndex对象后,就可以将这些对象作为参数,在创建Series或者DataFrame时传递给index。或者通过reindex方法更新已有的Series/DataFrame索引。

分层索引在重塑数据和数组透视表中非常重要。比如,我们可以使用unstack方法将数据在DataFrame中重新排列,也就是展开:

In [178]: s.unstack()
Out[178]:
          1         2         3
a  0.283490  0.295529  0.277676
b  0.487573       NaN  0.091161
c  0.285157 -0.806851       NaN
d       NaN -0.287969 -0.696511

In [179]: s.unstack().stack()  # 反操作stack
Out[179]:
a  1    0.283490
   2    0.295529
   3    0.277676
b  1    0.487573
   3    0.091161
c  1    0.285157
   2   -0.806851
d  2   -0.287969
   3   -0.696511
dtype: float64

对于DataFrame对象,每个轴都可以有分层索引,给index或columns提供一个多维数组,就可以分层:

In [3]: df = pd.DataFrame(np.arange(12).reshape((4, 3)),
   ...:                      index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
   ...:                      columns=[['Ohio', 'Ohio', 'Colorado'],
   ...:                               ['Green', 'Red', 'Green']])

In [4]: df
Out[4]:
     Ohio     Colorado
    Green Red    Green
a 1     0   1        2
  2     3   4        5
b 1     6   7        8
  2     9  10       11

可以给分层索引自定义名字:

In [5]: df.index.names
Out[5]: FrozenList([None, None])

In [6]: df.index.names = ['key1','key2']

In [7]: df.columns.names
Out[7]: FrozenList([None, None])

In [8]: df.columns.names = ['state','color']

In [9]: df
Out[9]:
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

In [10]: df['Ohio']
Out[10]:
color      Green  Red
key1 key2
a    1         0    1
     2         3    4
b    1         6    7
     2         9   10

1、 重排序和层级排序

有时候,我们需要重新排列轴上的层级顺序,或者按照特定的层级的值对数据进行排序。 Pandas的swaplevel方法用于这一功能,层级发生变更,但原数据不变。

In [11]: df.swaplevel('key1', 'key2')
Out[11]:
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方法只能在单一层级上对数据进行排序。在进行层级变换的时候,使用sort_index可以使得结果按照层级进行字典排序:

In [12]: df.sort_index(level=1)
Out[12]:
state      Ohio     Colorado
color     Green Red    Green
key1 key2
a    1        0   1        2
b    1        6   7        8
a    2        3   4        5
b    2        9  10       11

In [13]: df.swaplevel(0, 1).sort_index(level=0)
Out[13]:
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

sort_index(level=1)意思是在第2个层级上进行索引的排序。

swaplevel(0, 1)的意思是将第0层和第1层的行索引进行交换。

2、层级的汇总统计

使用level参数可以指定你想要在某个特定的轴上进行聚合。

In [15]: df.sum(level='key2')
Out[15]:
state  Ohio     Colorado
color Green Red    Green
key2
1         6   8       10
2        12  14       16

In [16]: df.sum(level='color', axis=1)
Out[16]:
color      Green  Red
key1 key2
a    1         2    1
     2         8    4
b    1        14    7
     2        20   10

3、使用DataFrame的列进行索引

在DataFarme的索引操作中,还有一个比较特殊的场景,就是将一些列转换成层级行索引:

In [17]: df= 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]})
    ...:

In [18]: df
Out[18]:
   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

In [19]: df2 = df.set_index(['c','d'])

In [20]: df2
Out[20]:
       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

In [21]: df.set_index(['c','d'],drop=False)
Out[21]:
       a  b    c  d
c   d
one 0  0  7  one  0
    1  1  6  one  1
    2  2  5  one  2
two 0  3  4  two  0
    1  4  3  two  1
    2  5  2  two  2
    3  6  1  two  3

In [22]: df2.reset_index()
Out[22]:
     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
  • set_index(['c','d']),将c列和d列变成了分层的行索引
  • drop=False则保留了原来的列数据
  • reset_indexset_index的反向操作

4、分层索引的取值与切片

先看针对Series的操作:

In [3]: tup = [('beijing',2000),('beijing',2019),
   ...:         ('shanghai',2000),('shanghai',2019),
   ...:         ('guangzhou',2000),('guangzhou',2019)]
In [4]: values = [10000,100000,6000,60000,4000,40000]
In [7]: index = pd.MultiIndex.from_tuples(tup)
In [8]: s = pd.Series(values, index=index)
In [9]: s
Out[9]:
beijing    2000     10000
           2019    100000
shanghai   2000      6000
           2019     60000
guangzhou  2000      4000
           2019     40000
dtype: int64

In [10]: s['beijing',2019]	#获取单个元素
Out[10]: 100000

In [11]: s['shanghai']	#局部取值得到一个新的Series
Out[11]:
2000     6000
2019    60000
dtype: int64

In [15]: s[:,2000]	#空切片取值
Out[15]:
beijing      10000
shanghai      6000
guangzhou     4000
dtype: int64

In [17]: s[s>5000]	#布尔掩码取值
Out[17]:
beijing    2000     10000
           2019    100000
shanghai   2000      6000
           2019     60000
guangzhou  2019     40000
dtype: int64

In [18]: s[['shanghai','guangzhou']] #花式索引取值
Out[18]:
shanghai   2000     6000
           2019    60000
guangzhou  2000     4000
           2019    40000
dtype: int64

再看看DataFrame,需要记住和理解的核心是:

  • 这是一个分层索引DataFrame对象,不是单级的
  • 默认以列为操作对象
In [19]: df = pd.DataFrame(np.arange(12).reshape((4, 3)),
    ...:             index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
    ...:             columns=[['Ohio', 'Ohio', 'Colorado'],
    ...:             ['Green', 'Red', 'Green']])
    ...:

In [20]: df
Out[20]:
     Ohio     Colorado
    Green Red    Green
a 1     0   1        2
  2     3   4        5
b 1     6   7        8
  2     9  10       11

In [23]: df['Ohio','Colorado']  # 不能这么做,因为列索引分层了
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)

In [24]: df[['Ohio','Colorado']]  # 这样可以
Out[24]:
     Ohio     Colorado
    Green Red    Green
a 1     0   1        2
  2     3   4        5
b 1     6   7        8
  2     9  10       11

In [25]: df['Ohio','Green']  # 每层提供一个参数
Out[25]:
a  1    0
   2    3
b  1    6
   2    9
Name: (Ohio, Green), dtype: int32

In [26]: df.iloc[:2,:2]  # 用隐式索引
Out[26]:
     Ohio
    Green Red
a 1     0   1
  2     3   4

In [28]: df.loc[:,('Ohio','Red')] # 这个比较难理解
Out[28]:
a  1     1
   2     4
b  1     7
   2    10
Name: (Ohio, Red), dtype: int32

另外最后要提醒的是:如果MultiIndex不是有序的索引,那么大多数切片操作都会失败!这时候可以使用前面介绍过的sort_index方法先排下序。

合并连接

可以通过多种方式将Pandas对象联合到一起:

  • pandas.merge: 根据一个或多个键进行连接。类似SQL的连接操作
  • pandas.concat:使对象在轴向上进行粘合或者‘堆叠’
  • combine_first:将重叠的数据拼接在一起,使用一个对象中的值填充另一个对象中的缺失值

1、merge连接

merge方法将两个pandas对象连接在一起,类似SQL的连接操作。默认情况下,它执行的是内连接,也就是两个对象的交集。通过参数how,还可以指定外连接、左连接和右连接。参数on指定在哪个键上连接,参数left_onright_on分别指定左右对象的连接键。

  • 外连接:并集
  • 内连接:交集
  • 左连接:左边对象全部保留
  • 右连接:右边对象全部保留
In [23]: df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
    ...:                     'data1': range(7)})
    ...:

In [24]: df2 = pd.DataFrame({'key': ['a', 'b', 'd'],
    ...:                     'data2': range(3)})
    ...:

In [25]: df1
Out[25]:
  key  data1
0   b      0
1   b      1
2   a      2
3   c      3
4   a      4
5   a      5
6   b      6

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

In [27]: pd.merge(df1,df2) # 默认内链接,并智能地查找连接的键
Out[27]:
  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

In [28]: pd.merge(df1,df2,on='key') # 最好是显式地指定连接的键
Out[28]:
  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

In [30]: pd.merge(df1, df2, how='outer') # 外连接
Out[30]:
  key  data1  data2
0   b    0.0    1.0
1   b    1.0    1.0
2   b    6.0    1.0
3   a    2.0    0.0
4   a    4.0    0.0
5   a    5.0    0.0
6   c    3.0    NaN
7   d    NaN    2.0

In [31]: pd.merge(df1, df2, how='left')  # 左连接
Out[31]:
  key  data1  data2
0   b      0    1.0
1   b      1    1.0
2   a      2    0.0
3   c      3    NaN
4   a      4    0.0
5   a      5    0.0
6   b      6    1.0

In [32]: pd.merge(df1, df2, how='right') #右连接
Out[32]:
  key  data1  data2
0   b    0.0      1
1   b    1.0      1
2   b    6.0      1
3   a    2.0      0
4   a    4.0      0
5   a    5.0      0
6   d    NaN      2

In [33]: df3 = pd.DataFrame({'lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
    ...:                     'data1': range(7)})
    ...:

In [34]: df4 = pd.DataFrame({'rkey': ['a', 'b', 'd'],
    ...:                     'data2': range(3)})
    ...:

In [35]: pd.merge(df3, df4, left_on='lkey', right_on='rkey') # 指定两边的键
Out[35]:
  lkey  data1 rkey  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连接是行的笛卡儿积。比如左边如果有3个‘b’行,右边有2个‘b’行,那么结果是3x2,6个‘b’行。

也可以同时指定多个键进行连接:

In [36]: left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],
    ...:                      'key2': ['one', 'two', 'one'],
    ...:                      'lval': [1, 2, 3]})

In [37]: right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
    ...:                       'key2': ['one', 'one', 'one', 'two'],
    ...:                       'rval': [4, 5, 6, 7]})

In [38]: pd.merge(left, right, on=['key1', 'key2'], how='outer')
Out[38]:
  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

merge操作中还有一个重叠列名的问题,比如上面的left和right两个数据,为此,我们可以使用suffixes参数,手动指定为重复的列名添加后缀:

In [41]: pd.merge(left, right, on='key1')
Out[41]:
  key1 key2_x  lval key2_y  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

In [42]: pd.merge(left, right, on='key1', suffixes=('_left', '_right'))
Out[42]:
  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合并的键可能是某个对象的行索引:

In [43]: left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'],
    ...:                       'value': range(6)})
    ...:

In [44]: right1 = pd.DataFrame({'group_val': [3.5, 7]}, index=['a', 'b'])

In [45]: left1
Out[45]:
  key  value
0   a      0
1   b      1
2   a      2
3   a      3
4   b      4
5   c      5

In [46]: right1
Out[46]:
   group_val
a        3.5
b        7.0

In [47]: pd.merge(left1, right1, left_on='key', right_index=True)
Out[47]:
  key  value  group_val
0   a      0        3.5
2   a      2        3.5
3   a      3        3.5
1   b      1        7.0
4   b      4        7.0

In [48]: pd.merge(left1, right1, left_on='key', right_index=True, how='outer')
Out[48]:
  key  value  group_val
0   a      0        3.5
2   a      2        3.5
3   a      3        3.5
1   b      1        7.0
4   b      4        7.0
5   c      5        NaN

使用right_index=True参数显式地指出,右边的对象right1使用它的行索引作为连接的键。

事实上Pandas有一个join方法,可以帮助我们直接用行索引进行连接:

In [49]: left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
    ...:                      index=['a', 'c', 'e'],
    ...:                      columns=['Ohio', 'Nevada'])
    ...:

In [50]: right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
    ...:                       index=['b', 'c', 'd', 'e'],
    ...:                       columns=['Missouri', 'Alabama'])
    ...:

In [51]: left2
Out[51]:
   Ohio  Nevada
a   1.0     2.0
c   3.0     4.0
e   5.0     6.0

In [52]: right2
Out[52]:
   Missouri  Alabama
b       7.0      8.0
c       9.0     10.0
d      11.0     12.0
e      13.0     14.0

In [53]: pd.merge(left2, right2, how='outer', left_index=True, right_index=True)
Out[53]:
   Ohio  Nevada  Missouri  Alabama
a   1.0     2.0       NaN      NaN
b   NaN     NaN       7.0      8.0
c   3.0     4.0       9.0     10.0
d   NaN     NaN      11.0     12.0
e   5.0     6.0      13.0     14.0

In [54]: left2.join(right2, how='outer') # 与上面的操作效果一样
Out[54]:
   Ohio  Nevada  Missouri  Alabama
a   1.0     2.0       NaN      NaN
b   NaN     NaN       7.0      8.0
c   3.0     4.0       9.0     10.0
d   NaN     NaN      11.0     12.0
e   5.0     6.0      13.0     14.0

2、轴向连接

concat方法可以实现对象在轴向的的粘合或者堆叠。

In [55]: s1 = pd.Series([0, 1], index=['a', 'b'])

In [56]: s2 = pd.Series([2, 3, 4], index=['c', 'd', 'e'])

In [57]: s3 = pd.Series([5, 6], index=['f', 'g'])

In [58]: pd.concat([s1, s2, s3]) # 要以列表的方式提供参数
Out[58]:
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: int64

In [59]: pd.concat([s1, s2, s3], axis=1) # 横向堆叠,但出现警告信息
C:\ProgramData\Anaconda3\Scripts\ipython:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
......

Out[59]:
     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

In [60]: pd.concat([s1, s2, s3], axis=1,sort=True) # 按人家的要求做
Out[60]:
     0    1    2
a  0.0  NaN  NaN
b  1.0  NaN  NaN
c  NaN  2.0  NaNDIP_GROUP
d  NaN  3.0  NaN
e  NaN  4.0  NaN
f  NaN  NaN  5.0
g  NaN  NaN  6.0

对于DataFrame,默认情况下都是按行往下合并的,当然也可以设置axis参数:

In [66]: df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=['a', 'b', 'c'],
    ...:                    columns=['one', 'two'])
    ...:

In [67]: df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=['a', 'c'],
    ...:                    columns=['three', 'four'])
    ...:

In [68]: df1
Out[68]:
   one  two
a    0    1
b    2    3
c    4    5

In [69]: df2
Out[69]:
   three  four
a      5     6
c      7     8


In [71]: pd.concat([df1, df2], sort=True)

Out[71]:
   four  one  three  two
a   NaN  0.0    NaN  1.0
b   NaN  2.0    NaN  3.0
c   NaN  4.0    NaN  5.0
a   6.0  NaN    5.0  NaN
c   8.0  NaN    7.0  NaN

In [72]: pd.concat([df1, df2], axis=1, sort=True)
Out[72]:
   one  two  three  four
a    0    1    5.0   6.0
b    2    3    NaN   NaN
c    4    5    7.0   8.0

append()方法 因为直接进行数组合并的需求非常普遍,所以 Series 和 DataFrame 对象都支持 append 方 法,让你通过最少的代码实现合并功能。例如,你可以使用 df1.append(df2),效果与 pd.concat([df1, df2]) 一样:

In[16]: print(df1); 
		print(df2); 
		print(df1.append(df2)) 

df1             df2           df1.append(df2)      
	A   B          A   B          A   B  
1  A1  B1      3  A3  B3     1   A1  B1  
2  A2  B2      4  A4  B4     2   A2  B2                 
							 3   A3  B3 
                             4   A4  B4

需要注意的是,与 Python 列表中的 append() 和 extend() 方法不同,Pandas 的 append() 不直接更新原有对象的值,而是为合并后的数据创建一个新对象。因此,它不能被称之为一 个非常高效的解决方案,因为每次合并都需要重新创建索引和数据缓存。总之,如果你需 要进行多个 append 操作,还是建议先创建一个 DataFrame 列表,然后用 concat() 函数一次 性解决所有合并任务。

3、联合叠加

有这么种场景,某个对象里缺失的值,拿另外一个对象的相应位置的值来填补。在Numpy层面,可以这么做:

In [74]: a = pd.Series([np.nan, 2.5, 0, 3.5, 4.5, np.nan],
    ...:               index=['f', 'e', 'd', 'c', 'b', 'a'])

In [75]: b = pd.Series([0, np.nan, 2.1, np.nan, np.nan, 5], index=list('abcdef'))

In [76]: a
Out[76]:
f    NaN
e    2.5
d    0.0
c    3.5
b    4.5
a    NaN
dtype: float64

In [77]: b
Out[77]:
a    0.0
b    NaN
c    2.1
d    NaN
e    NaN
f    5.0
dtype: float64

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

np.where(pd.isnull(a), b, a),这一句里,首先去pd.isnull(a)种判断元素,如果是True,从b里拿数据,否则从a里拿,得到最终结果。

实际上,Pandas为这种场景提供了一个专门的combine_first方法:

In [80]: b.combine_first(a)
Out[80]:
a    0.0
b    4.5
c    2.1
d    0.0
e    2.5
f    5.0
dtype: float64

对于DataFrame对象,combine_first逐列做相同的操作,因此你可以认为它是根据你传入的对象来‘修补’调用对象的缺失值。

In [81]: df1 = pd.DataFrame({'a': [1., np.nan, 5., np.nan],
    ...:                     'b': [np.nan, 2., np.nan, 6.],
    ...:                     'c': range(2, 18, 4)})
    ...:

In [82]: df2 = pd.DataFrame({'a': [5., 4., np.nan, 3., 7.],
    ...:                     'b': [np.nan, 3., 4., 6., 8.]})
    ...:

In [83]: df1
Out[83]:
     a    b   c
0  1.0  NaN   2
1  NaN  2.0   6
2  5.0  NaN  10
3  NaN  6.0  14

In [84]: df2
Out[84]:
     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

In [85]: df1.combine_first(df2)
Out[85]:
     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

重塑

对表格型数据进行重新排列的操作,被称作重塑或者透视。

使用多层索引进行重塑主要有stack和unstack操作,前面有介绍过。

In [93]: df = pd.DataFrame(np.arange(6).reshape(2,3),
    ...: index=pd.Index(['河南','山西'], name='省份'),
    ...: columns=pd.Index(['one','two','three'],name='number'))

In [94]: df
Out[94]:
number  one  two  three
省份
河南        0    1      2
山西        3    4      5

In [95]: result = df.stack() 

In [96]: result
Out[96]:
省份  number
河南  one       0
    two       1
    three     2
山西  one       3
    two       4
    three     5
dtype: int32

In [97]: result.unstack()
Out[97]:
number  one  two  three
省份
河南        0    1      2
山西        3    4      5

stack操作使得df的所有列都变成了分层行索引,产生了一个新的Series。

unstack默认情况下拆分最内层索引,然后将数据放入一个DataFrame中。可以传入一个层级序号或名称来拆分不同的层级。

In [98]: result.unstack(0)
Out[98]:
省份      河南  山西
number
one      0   3
two      1   4
three    2   5

In [99]: result.unstack('省份')
Out[99]:
省份      河南  山西
number
one      0   3
two      1   4
three    2   5

如果层级中的所有值并未有包含于每个子分组中,拆分可能会导致缺失值的产生:

In [100]: s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])

In [101]: s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])

In [102]: data2 = pd.concat([s1, s2], keys=['one', 'two'])

In [103]: data2  # 注意concat的结果是一个分层索引
Out[103]:
one  a    0
     b    1
     c    2
     d    3
two  c    4
     d    5
     e    6
dtype: int64

In [104]: data2.unstack()
Out[104]:
       a    b    c    d    e
one  0.0  1.0  2.0  3.0  NaN
two  NaN  NaN  4.0  5.0  6.0

In [105]: data2.unstack().stack() # 结果是可逆的
Out[105]:
one  a    0.0
     b    1.0
     c    2.0
     d    3.0
two  c    4.0
     d    5.0
     e    6.0
dtype: float64

In [106]: data2.unstack().stack(dropna=False) # 保留缺失值
Out[106]:
one  a    0.0
     b    1.0
     c    2.0
     d    3.0
     e    NaN
two  a    NaN
     b    NaN
     c    4.0
     d    5.0
     e    6.0
dtype: float64

而在DataFrame对象拆堆时,被拆的层级会变成结果中最低的层级:

In [107]: df = pd.DataFrame({'left': result, 'right': result + 5},
     ...:                   columns=pd.Index(['left', 'right'], name='side'))
     ...:

In [108]: df
Out[108]:
side       left  right
省份 number
河南 one        0      5
   two        1      6
   three      2      7
山西 one        3      8
   two        4      9
   three      5     10

In [109]: df.unstack('省份') # 因为作死引入了中文,所以版式不太对齐
Out[109]:
side   left    right
省份       河南 山西    河南  山西
number
one       0  3     5   8
two       1  4     6   9
three     2  5     7  10

GroupBy:分割、应用和组合

我们经常还需要对某些标签或索引的局部进行累计分析,这时就需要用到 groupby 了。虽然“分组”(group by)这个名字是借用 SQL 数据库语言的命令,但其理念引用发明 R 语言 frame 的 Hadley Wickham 的观点可能更合适:分割(split)、 应用(apply)和组合(combine)。

1、分割、应用和组合

一个经典分割 - 应用 - 组合操作示例如图所示:

image-20201215004909157

过程:

  1. 分割步骤将 DataFrame 按照指定的键分割成若干组。
  2. 应用步骤对每个组应用函数,通常是累计、转换或过滤函数。
  3. 组合步骤将每一组的结果合并成一个输出数组。

虽然我们也可以通过前面介绍的一系列的掩码、累计与合并操作来实现,但是意识到中间分割过程不需要显式地暴露出来这一点十分重要。而且 GroupBy(经常)只需要一行代码, 就可以计算每组的和、均值、计数、最小值以及其他累计值。GroupBy 的用处就是将这些步骤进行抽象:用户不需要知道在底层如何计算,只要把操作看成一个整体就够了。

用 Pandas 进行图所示的计算作为具体的示例。从创建输入 DataFrame 开始:

In[11]: df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'], 'data': range(6)}, columns=['key', 'data'])         
		df 
Out[11]:   key  data          
		0   A     0          
		1   B     1          
		2   C     2         
        3   A     3          
        4   B     4         
        5   C     5

我们可以用 DataFrame 的 groupby() 方法进行绝大多数常见的分割 - 应用 - 组合操作,将 需要分组的列名传进去即可:

In[12]: df.groupby('key') 
Out[12]: <pandas.core.groupby.DataFrameGroupBy object at 0x117272160>

需要注意的是,这里的返回值不是一个 DataFrame 对象,而是一个 DataFrameGroupBy 对象。 这个对象的魔力在于,你可以将它看成是一种特殊形式的 DataFrame,里面隐藏着若干组 数据,但是在没有应用累计函数之前不会计算。这种“延迟计算”(lazy evaluation)的方法使得大多数常见的累计操作可以通过一种对用户而言几乎是透明的(感觉操作仿佛不存在)方式非常高效地实现。
为了得到这个结果,可以对 DataFrameGroupBy 对象应用累计函数,它会完成相应的应用 / 组合步骤并生成结果:

In[13]: df.groupby('key').sum() 
Out[13]:      data          
		key 
		A       3         
        B       5          
        C       7

sum() 只是众多可用方法中的一个。你可以用 Pandas 或 NumPy 的任意一种累计函数,也可以用任意有效的 DataFrame 对象。

2、GroupBy对象

GroupBy 对象是一种非常灵活的抽象类型。在大多数场景中,你可以将它看成是 DataFrame 的集合,在底层解决所有难题。让我们用行星数据来做一些演示。
GroupBy 中最重要的操作可能就是 aggregate、filter、transform 和 apply(累计、过滤、转换、应用)了,现在先来介绍一些 GroupBy 的基本操作方法。

(1) 按列取值

GroupBy 对象与 DataFrame 一样,也支持按列取值,并返回一个修改过的 GroupBy 对象,例如:

In[14]: planets.groupby('method') 
Out[14]: <pandas.core.groupby.DataFrameGroupBy object at 0x1172727b8> 

In[15]: planets.groupby('method')['orbital_period'] 
Out[15]: <pandas.core.groupby.SeriesGroupBy object at 0x117272da0>

这里从原来的 DataFrame 中取某个列名作为一个 Series 组。与 GroupBy 对象一样,直到我们运行累计函数,才会开始计算:

In[16]: planets.groupby('method')['orbital_period'].median() 

Out[16]: method          
		 Astrometry                         631.180000         
         Eclipse Timing Variations         4343.500000          
         Imaging                          27500.000000   
         Microlensing                      3300.000000        
         Orbital Brightness Modulation        0.342887    
         Pulsar Timing                       66.541900      
         Pulsation Timing Variations       1170.000000    
         Radial Velocity                    360.200000 
         Transit                              5.714932      
         Transit Timing Variations           57.011000        
         Name: orbital_period, dtype: float64

这样就可以获得不同方法下所有行星公转周期(按天计算)的中位数。

(2) 按组迭代

GroupBy 对象支持直接按组进行迭代,返回的每一组都是 Series 或 DataFrame:

In[17]: for (method, group) in planets.groupby('method'):   
			print("{0:30s} shape={1}".format(method, group.shape)) 

Astrometry                     shape=(2, 6)
Eclipse Timing Variations      shape=(9, 6) 
Imaging                        shape=(38, 6)
Microlensing                   shape=(23, 6) 
Orbital Brightness Modulation  shape=(3, 6)
Pulsar Timing                  shape=(5, 6) 
Pulsation Timing Variations    shape=(1, 6) 
Radial Velocity                shape=(553, 6) 
Transit                        shape=(397, 6) 
Transit Timing Variations      shape=(4, 6)

尽管通常还是使用内置的 apply 功能速度更快,但这种方式在手动处理某些问题时非常有用。

(3) 调用方法

借助 Python 类的魔力(@classmethod),可以让任何不由 GroupBy 对象直接实现的方法直接应用到每一组,无论是 DataFrame 还是 Series 对象都同样适用。例如, 你可以用 DataFrame 的 describe() 方法进行累计,对每一组数据进行描述性统计:

In[18]: planets.groupby('method')['year'].describe().unstack() 

Out[18]:                                
							  count         mean       std     min      25%  \\ 
method 
Astrometry        		        2.0  2011.500000  2.121320  2010.0  2010.75 
Eclipse Timing Variations  		9.0  2010.000000  1.414214  2008.0  2009.00 
Imaging                        38.0  2009.131579  2.781901  2004.0  2008.00 
Microlensing                   23.0  2009.782609  2.859697  2004.0  2008.00 
Orbital Brightness Modulation 	3.0  2011.666667  1.154701  2011.0  2011.00 
Pulsar Timing            		5.0  1998.400000  8.384510  1992.0  1992.00 
Pulsation Timing Variations     1.0  2007.000000       NaN  2007.0  2007.00 
Radial Velocity               553.0  2007.518987  4.249052  1989.0  2005.00 
Transit     	              397.0  2011.236776  2.077867  2002.0  2010.00 
Transit Timing Variations       4.0  2012.500000  1.290994  2011.0  2011.75 

								50%      75%     max 
method 
Astrometry                     2011.5  2012.25  2013.0 
Eclipse Timing Variations      2010.0  2011.00  2012.0 
Imaging                        2009.0  2011.00  2013.0 
Microlensing                   2010.0  2012.00  2013.0 
Orbital Brightness Modulation  2011.0  2012.00  2013.0 
Pulsar Timing                  1994.0  2003.00  2011.0 
Pulsation Timing Variations    2007.0  2007.00  2007.0 
Radial Velocity                2009.0  2011.00  2014.0 
Transit                        2012.0  2013.00  2014.0 
Transit Timing Variations      2012.5  2013.25  2014.0

这张表可以帮助我们对数据有更深刻的认识,例如大多数行星都是通过 Radial Velocity 和 Transit 方法发现的,而且后者在近十年变得越来越普遍(得益于更新、更精确的望远镜)。最新的 Transit Timing Variation 和 Orbital Brightness Modulation 方法在 2011 年之后才有新的发现。

这只是演示Pandas 调用方法的示例之一。方法首先会应用到每组数据上,然后结果由 GroupBy 组合后返回。另外,任意 DataFrame / Series 的方法都可以由 GroupBy 方法调用, 从而实现非常灵活强大的操作。

3、累计、过滤、转换和应用

虽然前面的章节只重点介绍了组合操作,但是还有许多操作没有介绍,尤其是 GroupBy 对象的 aggregate()、filter()、transform() 和 apply() 方法,在数据组合之前实现了大量高效的操作。
为了方便后面内容的演示,使用下面这个 DataFrame:

In[19]: rng = np.random.RandomState(0)         
		df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],        
        				   'data1': range(6),                      
                           'data2': rng.randint(0, 10, 6)},   
                           columns = ['key', 'data1', 'data2'])       
        df 

Out[19]:   key  data1  data2          
		0   A      0      5    
        1   B      1      0       
        2   C      2      3         
        3   A      3      3         
        4   B      4      7         
        5   C      5      9

(1) 累计

我们目前比较熟悉的 GroupBy 累计方法只有 sum() 和 median() 之类的简单函数, 但是 aggregate() 其实可以支持更复杂的操作,比如字符串、函数或者函数列表,并且能一次性计算所有累计值。下面来快速演示一个例子:

In[20]: df.groupby('key').aggregate(['min', np.median, max]) 

Out[20]:       data1            data2                
			   min median max   min median max          
		 key
         A       0    1.5   3     3    4.0   5          
         B       1    2.5   4     0    3.5   7          
         C       2    3.5   5     3    6.0   9

另一种用法就是通过 Python 字典指定不同列需要累计的函数:

In[21]: df.groupby('key').aggregate({'data1': 'min', 'data2': 'max'}) 

Out[21]:      data1  data2          
		 key
		 A        0      5    
         B        1      7       
         C        2      9

(2) 过滤

过滤操作可以让你按照分组的属性丢弃若干数据。例如,我们可能只需要保留标准差超过某个阈值的组:

In[22]: 
def filter_func(x):     
			return x['data2'].std() > 4 

print(df); print(df.groupby('key').std()); 
print(df.groupby('key').filter(filter_func)) 

df                      df.groupby('key').std()    
   key  data1  data2    key      data1     data2 
0   A      0      5      A    2.12132  1.414214 
1   B      1      0      B    2.12132  4.949747 
2   C      2      3      C    2.12132  4.242641 
3   A      3      3 
4   B      4      7 
5   C      5      9 

df.groupby('key').filter(filter_func)   
  key  data1  data2 
1   B      1      0 
2   C      2      3 
4   B      4      7 
5   C      5      9

filter() 函数会返回一个布尔值,表示每个组是否通过过滤。由于 A 组 'data2' 列的 标准差不大于 4,所以被丢弃了。

(3) 转换

累计操作返回的是对组内全量数据缩减过的结果,而转换操作会返回一个新的全量数据。数据经过转换之后,其形状与原来的输入数据是一样的。常见的例子就是将每 一组的样本数据减去各组的均值,实现数据标准化:

In[23]: df.groupby('key').transform(lambda x: x - x.mean()) 

Out[23]:   data1  data2          
		0   -1.5    1.0          
		1   -1.5   -3.5          
		2   -1.5   -3.0          
		3    1.5   -1.0          
		4    1.5    3.5          
		5    1.5    3.0

(4) apply() 方法

apply() 方法让你可以在每个组上应用任意方法。这个函数输入一个 DataFrame,返回一个 Pandas 对象(DataFrame 或 Series)或一个标量(scalar,单个数 值)。组合操作会适应返回结果类型。
下面的例子就是用 apply() 方法将第一列数据以第二列的和为基数进行标准化:

In[24]: def norm_by_data2(x):            
			# x是一个分组数据的DataFrame         
            x['data1'] /= x['data2'].sum()          
         	return x 
	 
     	print(df); print(df.groupby('key').apply(norm_by_data2)) 

df                     df.groupby('key').apply(norm_by_data2)   
  key  data1  data2       key     data1  data2 
0   A      0      5     0   A  0.000000      5 
1   B      1      0     1   B  0.142857      0 
2   C      2      3     2   C  0.166667      3 
3   A      3      3     3   A  0.375000      3 
4   B      4      7     4   B  0.571429      7 
5   C      5      9     5   C  0.416667      9

GroupBy 里的apply() 方法非常灵活,唯一需要注意的地方是它总是输入分组数据的 DataFrame,返回 Pandas 对象或标量。具体如何选择需要视情况而定。

4、设置分割的键

前面的简单例子一直在用列名分割 DataFrame。这只是众多分组操作中的一种,下面将继续介绍更多的分组方法。

(1) 将列表、数组、Series 或索引作为分组键

分组键可以是长度与 DataFrame 匹配的任意 Series 或列表,例如:

In[25]: L = [0, 1, 0, 1, 2, 0] 
		print(df); print(df.groupby(L).sum()) 

df                     df.groupby(L).sum()   
  key  data1  data2        data1  data2 
0   A      0      5     0      7     17 
1   B      1      0     1      4      3 
2   C      2      3     2      4      7 
3   A      3      3 
4   B      4      7 
5   C      5      9

因此,还有一种比前面直接用列名更啰嗦的表示方法 df.groupby('key'):

In[26]: print(df); print(df.groupby(df['key']).sum()) 

df                        df.groupby(df['key']).sum()   
  key  data1  data2            data1  data2
0   A      0      5       A        3      8 
1   B      1      0       B        5      7 
2   C      2      3       C        7     12 
3   A      3      3 
4   B      4      7 
5   C      5      9

(2) 用字典或 Series 将索引映射到分组名称

另一种方法是提供一个字典,将索引映射到分组键:

In[27]: df2 = df.set_index('key')         
		mapping = {'A': 'vowel', 'B': 'consonant', 'C': 'consonant'}         
		print(df2); print(df2.groupby(mapping).sum()) 

df2                       df2.groupby(mapping).sum() 
key  data1  data2                    data1  data2
A        0      5         consonant     12     19 
B        1      0         vowel          3      8 
C        2      3 
A        3      3 
B        4      7 
C        5      9

(3) 任意Python 函数

与前面的字典映射类似,你可以将任意 Python 函数传入 groupby, 函数映射到索引,然后新的分组输出:

In[28]: print(df2); print(df2.groupby(str.lower).mean()) 

df2                       df2.groupby(str.lower).mean() 
key  data1  data2            data1  data2 
A        0      5         a    1.5    4.0 
B        1      0         b    2.5    3.5 
C        2      3         c    3.5    6.0 
A        3      3 
B        4      7 
C        5      9

(4) 多个有效键构成的列表

此外,任意之前有效的键都可以组合起来进行分组,从而返回 一个多级索引的分组结果:


In[29]: df2.groupby([str.lower, mapping]).mean() 

Out[29]:              data1  data2          
		 a vowel        1.5    4.0         
         b consonant    2.5    3.5          
         c consonant    3.5    6.0

标签:df,dtype,NaN,DataFrame,pd,Pandas,Out
From: https://www.cnblogs.com/simpleness/p/17624445.html

相关文章

  • [数据分析与可视化] Python绘制数据地图5-MovingPandas绘图实例
    MovingPandas是一个基于Python和GeoPandas的开源地理时空数据处理库,用于处理移动物体的轨迹数据。关于MovingPandas的使用见文章:MovingPandas入门指北,本文主要介绍三个MovingPandas的绘图实例。MovingPandas官方仓库地址为:movingpandas。MovingPandas官方示例代码仓库地址为:movin......
  • 【pandas小技巧】--列值的映射
    映射列值是指将一个列中的某些特定值映射为另外一些值,常用于数据清洗和转换。使用映射列值的场景有很多,以下是几种常见的场景:将字符串类型的列中的某些值映射为数字。例如,将“男”和“女”分别映射为0和1,以便进行机器学习算法的训练和预测。将缩写替换为全称。例如,将“USA......
  • 【pandas小技巧】--字符串转数值
    字符串转数字的用途和场景很多,其中主要包括以下几个方面:数据清洗:在进行数据处理时,经常会遇到一些数据类型不匹配的问题,比如某些列中的字符串类型被误认为是数字类型,此时需要将这些字符串类型转换为数字类型,才能进行后续的数值计算或统计分析。数据整理:有时候输入的原始数据可能......
  • Pandas 数据清洗
    Pandas数据清洗数据清洗是对一些没有用的数据进行处理的过程。很多数据集存在数据缺失、数据格式错误、错误数据或重复数据的情况,如果要对使数据分析更加准确,就需要对这些没有用的数据进行处理。在这个教程中,我们将利用Pandas包来进行数据清洗。1、Pandas清洗空值如果我们要删......
  • Pandas 处理CSV 文件
    PandasCSV文件CSV(Comma-SeparatedValues,逗号分隔值,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。CSV是一种通用的、相对简单的文件格式,被用户、商业和科学广泛应用。1、读取csv文件若需要该csv文件,自行下载:nba.csv文件importpa......
  • Pandas学习挑战第三关-数据结构DataFrame
    Pandas数据结构-DataFrameDataFrame是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔型值)。DataFrame既有行索引也有列索引,它可以被看做由Series组成的字典(共同用一个索引)。DataFrame构造方法如下:pandas.DataFrame(data,index,column......
  • Pandas库read_csv()中用于读取CSV文件的常用参数
    filepath_or_buffer---->CSV文件的路径或URL地址。sep---->CSV文件中字段分隔符,默认为逗号。delimiter---->CSV文件中字段分隔符,默认为None。header---->指定哪一行作为列名,默认为0,即第一行。names---->自定义列名,如果header=None,则可以使用该参数。index_col---->用作行索引的列......
  • 安装python3.8 所对应的pandas 1.3.3 的版本
    1、进入Anacondaprompt环境 2.进入虚拟环境开始安装  ......
  • pandas之filter
    数据准备importpandasaspdproduct_info={"订单号":["2951110000099262111","2181910000909928191","2194560000121355545","1194560000121311126","1483160000121315483"],"数量":[9......
  • 4个将Pandas换为交互式表格Python包
    Pandas是我们日常处理表格数据最常用的包,但是对于数据分析来说,Pandas的DataFrame还不够直观,所以今天我们将介绍4个Python包,可以将Pandas的DataFrame转换交互式表格,让我们可以直接在上面进行数据分析的操作。PivottablejsPivottablejs是一个通过IPythonwidgets集成到Python中的J......