作者:方跃文
Email: fyuewen@gmail.com
时间:始于2017年9月12日, 结束写作于
第五章笔记始于2018年1月6日,结束于2018年July 20th
这一章节虽然跨度很长,不过在1月到7月之间,pandas估计已经被上百次地应用到了我地实际研究中用于数据处理. The note on this chapter was finally finished today, I was quite happy because it has lasted quite a long time.
时间: 2017年1月6日清晨
panas 是原书作者Wes Mckinney主要想讨论的library,毕竟Mckinney本人就是pandas的开发者。pandas可以帮助数据分析工作,特别是在处理高级数据结构时候,pandas可以简化这些复杂的工作。pandas是基于NumPy构建的,因此会让以NumPy为中心的应用变得更加简单。尽管Pandas的构建哲学基于Numpy,但是两者也是有一处显著差别的,即 While pandas adopts many coding idioms from NumPy, the biggest difference is that pandas is designed for working with tabular or heterogeneous data. NumPy, by contrast, is best suited for working with homogeneous numerical array data.
Pandas其实是Mckinney在AQR量化投资管理公司任职时候开始编写的,最初他是为了满足工作需求才编写的。之后Pandas不断壮大,使得功能越来越丰富。不过Mckinney也认为后来的Pandas有点违背了自己的初衷,因为他其实是希望它能保留简介性和易用性。
在本书中,我们做如下的约定,即
In [1]:
from pandas import Series, DataFrame
In [2]:
import pandas as pd
这有点像之前那样的,我们约定 np 代表 numpy。因为 Series 和 DataFrame 用的次数非常多,所以将其引入命名空间会更加方便
In [3]:
from pandas import Series
a = [4, 5, 2, -4]
obj = Series(a)
In [4]:
obj
Out[4]:
In [5]:
obj2=obj+obj
obj2
Out[5]:
从上面我们可以看到,Series 的字符串表现形式为:索引在左边,值在右边。因为我们上面没有为数据指定特殊的索引,所以系统会自动创建一个 0 到 N-1 (N为数据的长度)的整数型索引。你可以通过 Series 的 values 和 index 属性获取数组表示形式和索引对象:
In [6]:
obj.values
Out[6]:
In [7]:
obj.index # 注意,如果使用python 2,显示的结果可能在形式上有点不同。
Out[7]:
大部分情况下,我们更希望去定制索引,以方便我们的使用。例如
In [9]:
obj2 = Series([4, 5, 2 ,-4], index=['d', 'b',
'a', 'c'])
In [10]:
obj2.values
Out[10]:
In [11]:
obj2.index
Out[11]:
In [12]:
obj2['a']
Out[12]:
我们可以看到上面,与普通的numpy数组相比,我们可以通过索引的方式选取Series中的单个或者一组值。
In [14]:
obj2[['a', 'b']]
Out[14]:
In [35]:
import pandas as pd
from pandas import Series, DataFrame
import numpy as np
obj11 = Series(np.arange(10))
print('value of obj11 is', obj11.values)
print('index of obj11 is', obj11.index)
obj13 = Series([1,2,3,4], index=['a','b','c','d'])
print('index of obj13 is', obj13.index)
print('value of obj13 is', obj13.values)
print('print the values that are large than 3\n', obj13[obj13>2])
print(obj13['b'])
print(obj13[['b','c']])
print(obj11[2])
obj13=obj13*2
print(obj13)
通过上面我们可以看出,pandas中的数组操作,都会保留索引和值之间的链接。
此外,还可以将Series看成一个定长的有序字典,因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中:
In [38]:
'b' in obj13
Out[38]:
In [39]:
'e' in obj13
Out[39]:
如果数据被存放在一个python字典中,也可以直接通过这个字典创建Series, 特别地,如果只传入一个字典,则Series中得到的索引就是原始字典中的键(有序排列)
In [43]:
sdata = {'Kyoto': 35000, "Tokyo": 40000, "Sendai": 30000, "Nagoya": 45000}
sdata
Out[43]:
In [54]:
obj4 = Series(sdata)
print(obj4)
In [52]:
cities = ['Kyoto', 'Osaka', 'Sendai', 'Nagoya']
obj5=Series(sdata, index=cities)
print(obj5)
在上例中,sdata 中跟cities索引相匹配的三个值会被找出来并放到相应的位置上,但是由于Osaka所对应的sdata值没有找到,所以它的值为NaN,即非数字(not a number)。在Pandas中,它用于表示确实或NA值。本书中,我们使用missing或者NA表示缺失的数据。pandas的 isnull 和 notnull 函数可以用于检测缺失数据。
In [55]:
pd.isnull(obj5)
Out[55]:
In [56]:
pd.notnull(obj5)
Out[56]:
Series 也有类似的实例方法:
In [58]:
obj5.isnull()
Out[58]:
In [57]:
obj5.notnull()
Out[57]:
在这一章节之后的内容中,我们将详细讲解如何处理缺失数据。
对于许多应用而言,Series最重要的功能之一就是,它在算术运算中会自动对其不同索引的数据。这句话不太好理解,我们可以通过看下面的例子加深了解。
In [59]:
obj4
Out[59]:
In [60]:
obj5
Out[60]:
In [62]:
obj5+obj4
Out[62]:
关于数据对其的功能,我们之后还会详细介绍,此处暂时跳过。
Series 对象本身以及索引都有一个name属性,该属性跟pandas其他的关键功能关系非常密切:
In [71]:
obj4.name = 'population'
In [72]:
obj4.index.name = 'cities'
In [73]:
obj4
Out[73]:
In [66]:
obj5.name = 'area'
In [67]:
obj5.index.name = 'cities'
In [68]:
obj5
Out[68]:
我们可以通过直接赋值的方式来修改Series的索引
In [70]:
obj5.index=['A', 'B', 'C', 'D']
print(obj5.index)
print(obj5)
Quiz: create a Series from dict, use the index from a to z. Finally name the Series.
In [19]:
import numpy as np
import pandas as pd
from pandas import Series
from numpy import random
import string
series_value = random.randn(26)
letter_string = string.ascii_lowercase[:]
letter_list = [i for i in letter_string]
sample_series = Series(series_value, index=letter_list)
sample_series.index.name = 'lower letters'
sample_series.name = 'random values'
print(sample_series)
DataFrame是一个表格型的数据结构。它既有行索引也有列索引,所以可以被看作是由Series组成的字典。跟其他类似的数据结构(如R的data.frame)比较,DataFrame中面向行和面向列的操作基本上是平衡的。Pandas中的DataFrame区别于一般python对象(如列表、字典等一维数据结构)的地方在于,它往往以一个或者多个二维块存放。DataFrame的内部技术细节十分复杂,作者说它的复杂程度远超于本书,所以不会做讨论,而只是讨论如何应用它。
虽然DataFrame以二维数据块的方式保存数据,但是我们在实际中还是可以轻松将这它们表示为更高维度的数据。表格型数据的特征是层次化索引,这是pandas高级数据处理中需要利用的关键要素。我们之后会讨论这方面的应用
构建DataFrame的方法有很多,最常用的是一种直接传入一个由等长列表或者NumPy数组组成的字典,例如
In [2]:
import pandas as pd
from pandas import DataFrame, Series
In [3]:
data = {'province':['Zhejiang', 'Zhejiang', 'Zhejiang', 'Hubei', 'Hubei'],'area':[101,101,101,185,185],
'pop':[98,98,98,100,100]}
In [5]:
frame1 = DataFrame(data)
frame1
Out[5]:
我们可以看到像Series一样,索引被自动加上,并且字典中的数据都被有序地排列。
如果我们指定列序列,那么DataFrame的列就会按照指定顺序进行排列:
In [6]:
DataFrame(data, columns=['province','area', 'pop'])
Out[6]:
同Series相同,如果传入的列在数据中是没有的,那么返回的也是NA值。
In [7]:
frame2 = DataFrame(data,columns=['province', 'pop', 'area', 'history'],index=[1,2,3,4,5])
frame2
Out[7]:
In [8]:
frame2.columns
Out[8]:
In [9]:
frame2.index
Out[9]:
通过类似字典标记的方式或属性的方式,可以将DataFrame中的列获取为一个Series
In [10]:
frame2['province'] #标记方式
Out[10]:
In [11]:
frame2.province #属性方式
Out[11]:
我们可以看出来,这个新得到的Series具有与原来DataFrame相同的索引,并且name属性也已经被相应地设置好了。
DataFrame中的行也可以通过位置或者名称的方式进行获取,例如,我们用索引字段ix:
In [13]:
frame2.ix[3] #ix is derepted in python3, instead use iloc
Out[13]:
列可以通过赋值的方式进行修改。例如,我们可以修改frame2中NaN的history列
In [21]:
frame2['history']=2000
In [22]:
frame2
Out[22]:
将列表或数组赋值给某个列时,它的长度必须和DataFrame中的长度相匹配(其实就是不能超过原DataFrame中的长度)。如果赋值的是一个Series,就会精确匹配DataFrame中的索引,所欲的空位都将被填上缺失值:
In [23]:
val = Series([2000,3000,2100],index=[1,3,5])
In [24]:
frame2['history']=val
In [25]:
frame2 #index 1,3,5的 被改变了,2和4因为在val中是没有的,所以填入的是NaN
Out[25]:
通过为不存在的列赋值,我们可以给DataFrame增加列,例如
In [29]:
frame2['estern']=frame2.province=="Zhejiang" #将boolean value赋值给estern这个列
In [30]:
frame2 #我们看到frame2中的确增加了一个estern列
Out[30]:
我们还可以利用 del 这个关键字来对列进行删除
In [31]:
del frame2['estern']
In [33]:
frame2.columns # estern coulumn was delted
Out[33]:
In [38]:
frame2["area"]=frame2["area"]+1
In [39]:
frame2
Out[39]:
特别注意 通过索引方式返回的列只是相应数据的视图而已,并不是副本。因此,对返回的Series所做的任何就地修改都会反映到源DataFrame中。通过Series的copy方法可以显示的赋值列。
此外,还有一种常见的数据形式是嵌套字典,也就是字典的字典:
In [55]:
pop = {'Nevada':{2001:2.4, 2002:2.9},
'Ohio':{2000:1.5, 2001: 1.7, 2002:3.6}}
如果把它传给DataFrame,它会被解释为:外层字典的键作为列,内层的键则作为行索引
In [57]:
frame3 = DataFrame(pop)
frame3
Out[57]:
我们也可以对结果进行转置
In [58]:
frame3.T
Out[58]:
内层字典的键会被合并、排序以形成最终的索引。如果显示指定了索引,则不会进行自动排序,例如:
In [65]:
DataFrame(pop, index=[2001,2002,2000])
Out[65]:
In [66]:
DataFrame(pop, columns=["Ohio", "Nevada"])
Out[66]:
In [67]:
DataFrame(pop, columns=["Ohio", "Nevada"],index=[2002,2001,2000])
Out[67]:
In [ ]:
这里,我重新复习一下上面DataFrame的内容。以原书数据作为参照。
In [20]:
#这里我们利用一种由列表组成的字典来创建DataFrame,后面我还会提及一种利用嵌套字典来创建的方式
import pandas as pd
import numpy as np
from pandas import DataFrame
data = {'state':['Ohio','Ohio','Ohio','Nevada','Nevada'],
'year':[2000,2001,2002,2001,2002],
'pop':[1.5, 1.7, 3.6, 2.4, 2.9]}
frame = DataFrame(data)
frame
Out[20]:
In [23]:
frame.iloc[1]
Out[23]:
In [ ]:
现在我们想一下,如何让这个表格按照'year','state','pop'顺序来排列?
In [7]:
f = DataFrame(data, columns = ['year', 'state', 'pop'])
f
Out[7]:
In [8]:
f.sort_values(by=['year'])
Out[8]:
In [9]:
#类似地,我们可以按照人口数量来从小到大排列。
f.sort_values(by=['pop'])
Out[9]:
In [51]:
#反过来,我们根据人口数量从大到小进行排序
df = f.sort_values(by=['pop'],ascending=False) #ascend means ‘go up'.
df
Out[51]:
现在来看看,如何让上面这个名字为df的table增加一个新的列,列的名字叫做area,并且填入随机数
In [53]:
sLength = len(df['year']) # 得到行的数目
print(sLength)
df1=df.assign(area=pd.Series(np.random.randn(sLength)).values)
df1
Out[53]:
In [ ]:
还有一种方法就是给一个新的列赋值,这也是增加列的方法:
In [54]:
df['area'] = pd.Series(np.random.randn(sLength)).values
df
Out[54]:
但是需要注意的是,上面assign的方法不会直接改原有的DataFrame,而后一种方法直接给新列赋值的方法是直接修改原始DataFrame的。因为在DataFrame中,任何通过索引方式返回的Series修改,都会反应到原始的DataFrame中。而如果想利用后一种方法,但是又不想改变原来的DataFrame,那么就首先要显示的创建一个副本。
下面我展示一个创建副本,并且对副本进行列删除的操作。
In [59]:
df2=df.copy()
print(df2)
print('\n')
del df2['area'] #delet a column in df2
print(df2)
print('\n')
print(df)
上面是对一个已经存在的DataFrame增加一个列。在利用上面的data字典进行构建DataFrame的时候,我们也可以增加一个列。例如:
In [24]:
frame2 = DataFrame(data, columns=['year','state','pop','debt'], index =['one',
'two',
'three',
'four',
'five'])
frame2
Out[24]:
In [25]:
frame2.columns
Out[25]:
上面我展示了如何对一个已经或者新构建的DataFrame增加一个列。现在我们来看看,利用类似字典标记的方式,如何来轻松获取DataFrame中的某个特别的列。我这里介绍两种方式:
In [34]:
#方法1
frame2['pop']
Out[34]:
In [36]:
#方法2
frame2.year #当我实用frame2.pop时候出现的结果和我预想的不同,这似乎是因为pop是python中毕竟特殊的关键字?
Out[36]:
In [ ]:
上面是从DataFrame中获取列,同样地我们也可以通过位置或名称地方式来获取行,比如用索引字段ix
In [37]:
frame2.ix['three']
Out[37]:
列可以通过赋值地方式进行修改。例如我们可以给frame2中地‘debt’列填入新数据:
In [40]:
frame2['debt']=16.5
frame2
Out[40]:
In [42]:
frame2['debt']=np.arange(5.) #give a list to this 'debt' columns
frame2
Out[42]:
值得注意的是,如果要用列表或者数组给DataFrame某个列赋值时,长度必须跟DataFrame长度一样。这也是为什么前面我在给一个已经存在的DataFrame增加新列的时候我首先利用len()得到了长度。
假设我们用一个不等的列表进行赋值时,我们会出现如下类似的错误。注意不管被赋值的数组是大于还是小于DataFrame的长度,都会出现错误。不要以为数组或者列表的长度比DataFrame的长度小,就惯性地认为会在一些位置上出现 NaN。因为DataFrame接受新数据的时候,不知道哪些位置上出现NaN,这样它自己就搞不清了,因此直接报错。
但是,如果是用Series去给DataFrame的列赋值时候,是允许长度比DataFrame的长度小的,因为Series是带index的,它可以一一匹配地去告诉DataFrame哪些位置是有数据的,而哪些地方没有并且需要用NaN填充。
In [43]:
frame2['debt']=np.arange(6.) #give a list to this 'debt' columns
frame2
In [48]:
#这里我们再试一下用Series来赋值
from pandas import Series
val = Series([-1.2, -1.5, -1.7], index = ['two', 'four', 'five'])
frame2['debt']=val
frame2
Out[48]:
In [ ]:
In [60]:
Out[60]:
In [ ]:
这里我再提及一次利用字典嵌套方式来创建DataFrame的方法:
In [ ]:
In [ ]:
Indexing into a Series
In [3]:
import pandas as pd
import numpy as np
obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
obj
Out[3]:
In [4]:
type(obj)
Out[4]:
In [5]:
obj[obj<2]
Out[5]:
Indexing into a DataFrame: it retrieves one or more columns either with a single value or sequence
In [7]:
data = pd.DataFrame(np.arange(16).reshape(4,4),
index=['Ohio', 'Colorado','Utah', 'New York'],
columns=['one', 'two', 'three', 'four'])
data
Out[7]:
In [8]:
data['two']
Out[8]:
In [10]:
data[['two', 'one']]
Out[10]:
In [ ]:
Slice DataFrame
In [11]:
data[::2]
Out[11]:
In [12]:
data[:2]
Out[12]:
In [ ]:
Select data with a bollean array
In [13]:
data[data['three'] > 5]
Out[13]:
In [14]:
data[data['three'] > 3]
Out[14]:
Special indexing operators loc and iloc can be used to select the subsect of the rows and columns from a DataFrame with numpy-like notation usng either axis labels (loc) or integers (iloc).
In the first example, let's select a single row and multi columns by label:
In [15]:
data.loc['Colorado']
Out[15]:
In [16]:
data.loc['Colorado', ['two', 'four']]
Out[16]:
Now, let's use interger indexing method iloc to select the same subset as selected in last cell by loc:
In [19]:
data.iloc[1,[1, 3]]
Out[19]:
In [ ]:
In [26]:
data.loc[:"Colorado"]
Out[26]:
In [61]:
data.loc[:"Colorado"][:1]
Out[61]:
In [27]:
data.loc["Colorado"]
# Compared to the last cell, I remove the comma :
# Hence only the row of 'Colorado'
Out[27]:
In [25]:
data.loc[:"Colorado", ['two', 'three']]
Out[25]:
In [28]:
data
Out[28]:
In [35]:
data.iloc[[1,2],[1, 3]]
Out[35]:
In [43]:
data.iloc[:, 1:3][data.three > 6]
Out[43]:
As you can see above, we have many ways to select and rerange the data contained in a pandas project
In [46]:
data.at['Utah', 'two']
Out[46]:
In [49]:
data.iat[2, 1]
Out[49]:
In [50]:
data.reindex
Out[50]:
In [56]:
data.get_value
Out[56]:
In [59]:
data.set_value
Out[59]:
In [64]:
#For example
import numpy as np
data_list = np.arange(10).tolist()
data_list
Out[64]:
In [65]:
data_list[:2]
Out[65]:
In [78]:
data_df = pd.DataFrame(np.arange(9).reshape((3,3)),
index=['0', '1', '2'],
columns=['a', 'b', 'c'])
data_df
Out[78]:
In [79]:
data_df.iloc[:2]
Out[79]:
In [93]:
series_test = data_df.a
series_test
Out[93]:
In [118]:
series = series_test[:]*0.1
type(series)
Out[118]:
In [111]:
type(series_test)
Out[111]:
In [112]:
ser = pd.Series(np.arange(3.))
print(type(ser))
ser
Out[112]:
In [114]:
ser.loc[:2.]
Out[114]:
In [121]:
import pandas as pd
import numpy as np
from numpy.random import randn
s1 = pd.Series(randn(4), index=['a', 'b', 'c', 'd'])
Out[121]:
In [123]:
s2 = pd.Series(randn(4), index=['e', 'b', 'c', 'd'])
s2
Out[123]:
In [124]:
s1 + s2
Out[124]:
由于s1和s2中的index有一部分是不重合的,所以那些部分就出现了 NaN。这类数据在pandas的其他算数运算中也会进行类似地传递,即以 NaN 的形式出现。
In [3]:
import pandas as pd
import numpy as np
df1 = pd.DataFrame(np.arange(9).reshape((3,3)),
columns = list('bcd'),
index=['Ohio', 'Texas', 'Colorado'])
In [4]:
df2 = pd.DataFrame(np.arange(12).reshape((4,3)),
columns = list('bde'),
index=['Utah', 'Ohio', 'Texas', 'Oregon'])
In [5]:
df1
Out[5]:
In [6]:
df2
Out[6]:
对上述两个dataframe求和的结果中,所有在df1和df2中出现的index和columns都会出现,对于那些没有重合的index或者columns,则以NaN的结果出现。
In [7]:
df1 + df2
Out[7]:
假设两个dataframe中没有index或者column是一致的,那么他们运算的结果会得到所有都是NaN的dataframe 例如
In [8]:
df1 = pd.DataFrame({'A': [1,2]})
df2 = pd.DataFrame({'B': [3,4]})
print(df1)
print(df2)
In [9]:
df1 + df2
Out[9]:
相比于上个cell,我们改变一下做法,use fill_value to pass an argument (here, I use 0)
In [10]:
df1.add(df2, fill_value=0)
Out[10]:
Table: 灵活多变的算数方法
Method | description |
---|---|
add, radd | + |
sub, rsub | - |
div, rdiv | / |
floordiv, rfloordiv | floor division // |
mul, rmul | methods for multiplication (*) |
pow, rpow | methods for exponentiation (**) |
注意:加了字母 r 的,代表对应的 courterpart
例如 1/df1 和 df1.rdiv(1) 是等价的。
In [12]:
1/df1
Out[12]:
In [14]:
df1.rdiv(1)
Out[14]:
In [23]:
df1.floordiv(df2, fill_value=4)
# about floordiv, you can see https://www.quora.com/What-does-floor-division-in-Python-do
Out[23]:
In [24]:
df1.mul(df2,fill_value=1)
Out[24]:
In [26]:
df1.pow(df2, fill_value=3)
Out[26]:
In [28]:
arr = np.arange(12.).reshape((3,4))
arr
Out[28]:
In [29]:
arr[0]
Out[29]:
In [30]:
arr - arr[0]
Out[30]:
这就叫做 broadcasting。后面有一个专门的章节会介绍broadcasting。DataFrame和Series之间的运算也接近与此。
In [32]:
frame = pd.DataFrame(np.arange(12.).reshape((4,3)),
columns = list('bde'),
index = ['Utah', 'Ohio', 'Texas', 'Oregon'])
In [33]:
frame
Out[33]:
In [35]:
series = frame.iloc[0]
print(series)
By default, DataFrame and Series 之间的算数运算会把Series的index匹配到DataFrame的列,然后沿着行一直向下广播
In [37]:
frame - series
Out[37]:
不过,假设Series中的索引和DataFrame中的列没有一一对应会发生什么呢?我们来看一个例子
In [39]:
series2 = pd.Series(range(3), index= ['b', 'e', 'f'])
frame + series2
Out[39]:
从上面我们可以看到,假设Series的index和DataFrame的Columns没有overlap,参与运算的的两个对象就会被重新索引以形成并集
In [ ]:
上面我们介绍的是去匹配DataFrame中的列,然后沿着行向下braodcast,那么如果我们希望匹配行,沿着列广播呢?这是必须使用 上述表格中的算数运算方法。 例如: $\star$$\star$$\star$$\star$$\star$$\star$$\star$IMPORTANT$\star$$\star$$\star$$\star$$\star$$\star$
In [44]:
series3 = frame['d']
series
Out[44]:
In [45]:
frame.sub(series3, axis=0) # 这里传入的轴号0就是希望匹配的轴。在本立中,我们的目的是匹配DataFrame中的行索引,并进行广播
Out[45]:
In [49]:
from pandas import DataFrame
import numpy as np
frame = DataFrame(np.random.randn(4,3), columns = list('bde'),
index = ['Utah', 'Ohio', 'Texas', 'Oregon'])
In [50]:
frame
Out[50]:
In [51]:
np.abs(frame)
Out[51]:
另外一个常见的操作是,将函数应用到由各行或者列形成的1D array上。DataFrame的apply方法即可实现这样的功能
In [52]:
f = lambda x: x.max() - x.min()
In [54]:
frame.apply(f)
# alternatively, we can use frame.apply(f, axis='index')
# or frame.apply(f, axis=0)
Out[54]:
In [58]:
frame.apply(f, axis=1)
# alternatively, we can use frame.apply(f, axis='columns')
# or frame.apply(f, axis=1)
Out[58]:
对于python使用者方便的是,大部分在数组中的统计函数(例 sum、mean)等也都是DataFrame的方法,因此大部分情况下并不需要使用apply方法。 例如
In [63]:
np.sum(frame, axis='index')
Out[63]:
In [64]:
np.sum(frame, axis='columns')
Out[64]:
除了标量之外,传递给apply的函数还可返回由多个值组成的Series:
In [66]:
# using this function
# we can print the maximum or minimum for each column of a DataFrame
from pandas import Series
def F(x):
return Series([x.min(), x.max()], index =
['min', 'max'])
frame.apply(F)
Out[66]:
In [ ]:
此外,元素级的Python函数也是可以使用的。
In the following example, I will show you how to format the flowaing, i.e. keep specifiec figures for the floatings
In [6]:
import pandas as pd
from pandas import DataFrame, Series
import numpy as np
frame = DataFrame(np.random.randn(3,4), columns = [list('abcd')],
index=[list('123')])
format = lambda x: '%.2f' % x
print(frame)
df = frame.applymap(format)
In [7]:
df
Out[7]:
The reason why we call it 'applymap' is that Series itself has a 'map' method for applying an element-wise function:
In [19]:
series = Series([1.111,2.111,3.111], index=['a', 'c', 'b'])
In [22]:
series.map(format)
Out[22]:
In [1]:
from pandas import DataFrame, Series
obj = Series(range(4), index = ['d', 'a', 'b', 'c'])
In [2]:
obj
Out[2]:
In [4]:
obj.sort_index()
Out[4]:
我们注意到,上面对这个Series根据index进行了重新排序。
对于DataFrame,我们可以对按照任意任意轴进行排序。 例如
In [9]:
import numpy as np
frame = DataFrame(np.random.randn(4,3), columns=[list('dbc')],
index=np.arange(4).tolist())
frame
Out[9]:
In [14]:
frame.sort_index(axis=0, ascending=False)
Out[14]:
In [15]:
frame.sort_index(axis=1, ascending=True)
Out[15]:
上面,我们是对索引进行排序。如果要按照值对Series排序,则可以用order方法
In [16]:
obj = Series([3,12,-222,3])
In [18]:
obj.sort_values()
# note that in python 2.7: obj.order()
# I highly recommend using the new feature in python3
Out[18]:
The missing values are sorted to the end of the Series by default, see exmaple:
note that: we use numpy.nan to represent the missing value in DataFrame or Series
In [20]:
import pandas as pd
obj = pd.Series([45, np.nan, 72, 83, np.nan, -33 ])
In [21]:
obj
Out[21]:
In [25]:
obj.sort_values()
Out[25]:
For DataFrame, 我们可以根据需要对一个或者多个列中的值进行排序。讲一个或者多个列的名字传递给 by option of sort_values
In [44]:
frame = pd.DataFrame({'b':[4, 7, -3, 2], 'a':[0, 1.1, 0, 1.1]})
In [41]:
frame
Out[41]:
In [45]:
frame.sort_values(by='b')
Out[45]:
In [46]:
frame.sort_values(by=['a', 'b'])
Out[46]:
Ranking 的排名值从1开始,直到数组中有效数组的数量。rank 方法默认会通过‘为各组分配一个平均排名’的方式来破坏平级关系:
In [47]:
obj = Series([7, -5, 7, 4, 2, 0, 4])
In [48]:
obj.rank() #表示在这个数在原来的向量中排第几名,有相同的数,取平均
Out[48]:
此外,我们也可根据值在原数据中出现的顺序给出排名:
In [50]:
obj.rank(method = 'first') # 注意,和上一个cell中方法的区别;
Out[50]:
我们也可以按照降序进行排名
In [51]:
obj.rank(ascending=False, method='max') # 使用整个分组的最大排名
Out[51]:
In [52]:
obj.rank(ascending=False, method='min') # 使用整个分组的额最小排名
Out[52]:
In [53]:
obj = Series(range(5), index=[list('aabbc')])
In [54]:
obj
Out[54]:
Using 'is_unique' can check whether the labels are unique or not:
In [55]:
obj.index.is_unique
Out[55]:
对具有重复值的索引,数据选取行为会有些不同。 如果索引对应着多个值,那么返回的是一个 Series.原书英文第二版说,如果单值情况下,只是返回一个标量。但是我的python3环境下,单值情况下返回来的还是Series,这个可能是日后得注意的地方。 无论如何,对于index,还是尽量唯一确定比较好。避免在数据的处理中出现非不必要的错误。
In [61]:
a_series = obj['a']
print(type(a_series))
a_series
Out[61]:
In [62]:
c_value = obj['c']
print(type(c_value))
print(c_value)
In [63]:
obj['c']
Out[63]:
这里我再举一个DataFrame的例子,但其实和Series的用法一致
In [67]:
df = pd.DataFrame(np.random.randn(4,3), index=[list('aabb')])
In [68]:
df
Out[68]:
In [69]:
df.loc['b']
Out[69]:
In [1]:
import pandas as pd
import numpy as np
df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5],
[np.nan, np.nan], [0.75, -1.3]],
index = [list('abcd')],
columns = ['one', 'two'])
In [2]:
df
Out[2]:
调用DataFrame的sum方法能返回一个含有列小计的Series
In [3]:
df.sum()
Out[3]:
In [4]:
#Passing axis = 'columns' or axis = 1 sums across the columns instead
df.sum(axis=1)
Out[4]:
!!! Attention: 原书第二版中的结果在c的地方是NaN,而且我在bidner里面测试了下原作者的notebook(https://github.com/wesm/pydata-book) 的确,也是NaN。但是即使我copy他的代码到我现在这个环境中,c的位置也称为了0.00
In [ ]:
如果我们不想对存在NaN的值进行运算,我们可以使用
In [5]:
df.sum(axis=1, skipna=False) #还有一个option是level,适用于在multiindex的情况下,目前我还没有碰到过
Out[5]:
在Pandas中,一些方法例如 idxmin和idxmax返回的是间接统计,比如达到最小值和最大值的索引。例如下面的例子
In [6]:
df.idxmax()
Out[6]:
In [ ]:
另一些方法则是直累计型的,如cumsum
In [7]:
df.cumsum()
Out[7]:
In [ ]:
还有一些方法,则既不是约简型也不是累计行。例如
describe方法(在实际当中,这个方法被使用的相当频繁,因为可以快速的直到一个dataset大致的情况)
In [8]:
df.describe()
Out[8]:
对于非数值型数据,describe会产生另外一种汇总统计:
In [16]:
obj = pd.Series(['a', 'a', 'b', 'c']*4)
In [17]:
obj
Out[17]:
In [22]:
obj.describe()
Out[22]:
这里会用到来自于Yahoo!Finance的数据(stock price and volumes,即股票价格和成交量)。 需要用到andas-datareader这个pacakge,如果没有的话,需用用conda或者pip安装一下,conda的话是 conda install pandas-datareader
In the book, it get_data_yahoo, however, we cannot use it now becaues the API of yahoo was changed last year as said by https://github.com/pydata/pandas-datareader/
In [18]:
import pandas_datareader.data as web
import pandas as pd
all_data = {ticker: web.get_data_robinhood(ticker)
for ticker in ['AAPL']}
price = pd.DataFrame({ticker: data['low_price'] #Adj Close
for ticker, data in all_data.items()})
# volume = pd.DataFrame({ticker: data['symbol']
# for ticker, data in all_data.items()})
In [19]:
price.head(10)
Out[19]:
In [ ]:
In [21]:
price_aapl = price['AAPL']
type(price_aapl)
Out[21]:
In [34]:
price_float_appl = pd.to_numeric(price_aapl)
In [37]:
returns = price_float_appl.pct_change()
returns.tail()
Out[37]:
In [ ]:
corr 方法可以计算两个Series中重叠的、非NA的、按索引对齐的值的相关系数。这个我在具体的研究中也使用过。详细的请看书本好了。 因为我这里值找到了AAPL的数据,因此只有一个series。 不过为了说明我简单演示一下,只是利用copy方法做了一个副本。当然这俩series是完全相同的,所以相关系数肯定是1
与此类似的,cov 用于计算协方差即covariance
In [41]:
returns_copy = returns.copy()
In [42]:
returns.corr(returns_copy)
Out[42]:
In [43]:
returns.cov(returns_copy)
Out[43]:
In [53]:
obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])
obj
Out[53]:
In [61]:
uniques = obj.unique()
如果需要排序的话,
In [62]:
uniques
Out[62]:
In [65]:
sorted(uniques)
Out[65]:
In [66]:
obj.value_counts()
Out[66]:
从上面可以看出来,结果默认是按照频率降序排序的。value_counts 还是一个顶级的pandas方法,可以用于任何数组 或者序列
In [74]:
pd.value_counts(obj.values, sort=False)
#value_counts returns a Series
#containing unique values as its index and frequencies as its values,
#ordered count in descending order
Out[74]:
In [68]:
obj
Out[68]:
In [69]:
mask = obj.isin(['b', 'c'])
In [71]:
mask
Out[71]:
In [73]:
type(mask)
Out[73]:
In [72]:
obj[mask]
Out[72]:
In some cases, you may want to compute a histogram on multiple realted columns in a DataFrame. Here, I show an example:
In [81]:
data = pd.DataFrame({'Qu1': [1, 3, 4, 3, 4],
'Qu2': [2, 3, 1, 2, 3],
'Qu3': [1, 5, 2, 4, 4]})
In [82]:
data
Out[82]:
将 pd.value_counts 传给DataFrame的apply函数,将得到
In [86]:
result = data.apply(pd.value_counts).fillna(0) # 这个以后得重新复习一下。
In [87]:
result
Out[87]:
In [90]:
import pandas as pd
import numpy as np
string_data = pd.Series(['abc', 'dh', np.nan, 'xh'])
In [91]:
string_data
Out[91]:
Python 中内置的None值,也会被作为NA处理
In [92]:
string_data[0] = None
In [93]:
string_data.isnull()
Out[93]:
NA处理常见方法: dropna, fillna, isnull, notnull.
In [94]:
from numpy import nan as NA
data = pd.Series([1, NA, 3, NA, 7])
In [95]:
data
Out[95]:
In [96]:
data.dropna()
Out[96]:
也可以通过Bollean型索引达到这个目的
In [97]:
data[data.notnull()]
Out[97]:
In [ ]:
对于DataFrame,事情会复杂一些,得区分一下,是丢弃全NA或含有NA的行或者列。
dropna默认丢失任何含有缺失值的行:
In [98]:
data = pd.DataFrame([[1., 6.5, 3], [1., NA, NA],
[NA, NA, NA], [NA, 6.5, 3.]])
In [99]:
data
Out[99]:
In [101]:
data.dropna()
Out[101]:
In [103]:
data.dropna(how='all') # by defualt, axis=0,也就是行全含NA时,才被丢弃
Out[103]:
In [104]:
data.dropna(how='all', axis=1) # 列全为NA时,被丢弃
Out[104]:
fillna可以用于填充丢失的数据。例如
In [105]:
newdata = data.fillna(2.0)
In [106]:
newdata
Out[106]:
In [107]:
import pandas as pd
import numpy as np
data = pd.Series(np.random.randn(10),
index = [['a', 'a',
'a', 'b',
'b', 'b',
'c', 'c',
'd', 'd'],
[1, 2, 3, 1, 2, 3, 1, 2, 2, 3]])
In [108]:
data
Out[108]:
上面就是一个简单的,所有两个层次索引的数据。如果要选取数据的子集,我们可以使用:
In [109]:
data['b']
Out[109]:
进一步地,我们可以对第二层索引:
In [115]:
data['b'][1]
Out[115]:
或者
In [116]:
data['b', 1]
Out[116]:
层次化索引对实际生活和工作中的数据处理非常有帮助。 例如,上面data中的数据,我们可以使用unstack方法将它门重新 分配到一个DataFrame中去
In [117]:
data.unstack()
Out[117]:
unstack的逆运算是stack:
In [118]:
data.unstack().stack()
Out[118]:
对于DataFrame来说,每条轴都可以有分层索引:
In [121]:
frame = 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 [122]:
frame # 下面中,会打印出轴标签
Out[122]:
In [125]:
frame.index.names = ['key1', 'key2']
frame.columns.names = ['state', 'color']
In [126]:
frame
Out[126]:
这样,我们就能方便地对这个DataFrame进行数据选取
In [127]:
frame['Ohio']
Out[127]:
In [128]:
frame.swaplevel('key1', 'key2')
Out[128]:
为了上面重新分级的数据有一个排序。我们还可以用到sortlevel
In [131]:
frame.swaplevel('key1', 'key2').sort_index(level='key2')
Out[131]:
In [135]:
frame.sum(level='color', axis=1)
Out[135]:
In [136]:
frame.sum(level='key2', axis=0)
Out[136]:
这些其实是利用了pandas的groupby功能,以后还会继续介绍。
In [144]:
frame = 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 [151]:
frame_back = frame.copy()
frame_back
Out[151]:
In [148]:
frame2 = frame.set_index(['c'])
frame3 = frame.set_index(['c', 'd'])
In [147]:
frame2
Out[147]:
In [149]:
frame3
Out[149]:
如果是想在原始DataFrame上改,可以用
In [152]:
frame_back.set_index(['c'], inplace=True)
In [153]:
frame_back
Out[153]:
上面的操作中,当我门把某列作为行索引时,它自动就从列中移除了。如果我们想保留它们,可以加上drop操作
In [154]:
frame.set_index(['c'], drop=False)
Out[154]:
与set_index相反,reset_index则是可以把层次化索引转化到列中。
In [155]:
frame2.reset_index()
Out[155]:
In [ ]:
In [ ]:
In [ ]:
In [2]:
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
In [3]:
ts = pd.Series(np.random.randn(1000), index = pd.date_range(
'1/1/2000', periods = 1000))
ts = ts.cumsum()
In [4]:
ts.plot()
Out[4]:
In [5]:
df = pd.DataFrame(np.random.randn(1000, 4),
index = ts.index, columns = list('ABCD'))
In [6]:
df = df.cumsum()
In [7]:
plt.figure(); df.plot();
In [8]:
df3 = pd.DataFrame(np.random.randn(1000,2), columns
= ['B', 'C']).cumsum()
In [9]:
df3['A'] = pd.Series(list(range(len(df))))
In [10]:
#pass logy to get a log-scale y-axis
ts = pd.Series(np.random.randn(1000), index=pd.date_range('1/1/2000', periods=1000))
ts = np.exp(ts.cumsum())
ts.plot(logy=True)
Out[10]:
In [11]:
df.A.plot()
Out[11]:
In [12]:
df.B.plot(secondary_y=True, style='g')
Out[12]:
In [14]:
#To plot some columns in a DataFrame, give the column names to the secondary_y keyword:
plt.figure()
Out[14]:
In [15]:
ax = df.plot(secondary_y=['A', 'B'])
In [16]:
ax.set_ylabel('CD scale')
Out[16]:
In [17]:
ax.right_ax.set_ylabel('AB scale')
Out[17]:
In [26]:
plt.figure()
# plt.show()
Out[26]:
In [28]:
df.plot(secondary_y=['A', 'B'], mark_right=False) #set false to turn off the automatic marking
Out[28]:
In [ ]:
In [ ]:
In 2017, Dainiel Chen gave a very nice presentation in SciPy meeting. I watched his vedio in the (youtube)[https://www.youtube.com/watch?v=oGzU688xCUs], hence I added some note in this section. All the imported files mentioned in this section can be found in Chen's github (repo.)[https://github.com/chendaniely/scipy-2017-tutorial-pandas]. For my convenience, I moved the folder 'data' in his repo to chapter05 in my current repo.
In [1]:
import pandas as pd
In [2]:
#check the version of the padas you are using
pd.__version__
Out[2]:
In [3]:
print('the version of pandas in my computer is', pd.__version__)
In [4]:
pd.read_csv('./chapter05/data/gapminder.tsv', delimiter='\t').head(10)
Out[4]:
In [5]:
pd.read_csv('./chapter05/data/gapminder.tsv', delimiter='\t').head() # 如果括号中不指定,默认是5,所以只打印出表格的前面五行
Out[5]:
In [6]:
#let us specify df for the imported file
df = pd.read_csv("./chapter05/data/gapminder.tsv", delimiter='\t')
In [7]:
df
Out[7]:
In [8]:
df.head(10)
Out[8]:
In [9]:
type(df)
Out[9]:
In [10]:
df.shape
Out[10]:
In [20]:
df.shape() #you will see an error because as you see above, df.shape is a tuple
In [11]:
df.info()
In [ ]: