Pandas基础-利用python进行数据分析

mac2024-06-06  40

Pandas入门

git地址:https://github.com/codebysandwich/DataScience/tree/master/pandas pandas是数据分析时主要的工具,经常结合numpy,scipy及matplotlib一起使用。pandas支持大部分numpy数组风格的计算,尤其是使用数组函数而非大量的for循环。


尽管pandas采用很多numpy的代码风格,但最大的不同在于pandas是用来处理表格型或异质型数据的。


导入方式: import pandas as pd


Pandas数据结构介绍

pandas中最重要的三种基础数据结构:index, Series, DataFrame。下面主要说Series,DataFrame。尽管不能解决所有的问题,但是他们为大多数应用提供了有效、易用的基础。

Series

Series是一种一维的数组型对象,包含值序列和索引(index),最简单的序列可以由一个数组组成。

import numpy as np import pandas as pd obj = pd.Series([4, 7, -5, 3]) obj 0 4 1 7 2 -5 3 3 dtype: int64

交互式环境中,索引在左边,值在右边。由于没有指定索引,自动生成0到N-1(N为数组长度)。可以通过values和index来捕获Series的值和索引:

obj.values array([ 4, 7, -5, 3], dtype=int64) obj.index RangeIndex(start=0, stop=4, step=1)

通常需要创建一个索引序列来作为数据的标签:

obj2 = pd.Series([4, 7, -5, 3], index=list('dbac')) # 注意index使用list类型传递 obj2 d 4 b 7 a -5 c 3 dtype: int64 obj2.index Index(['d', 'b', 'a', 'c'], dtype='object')

相比数组类型,可以在原先的默认索引和标签索引来进行索引:

obj2[1] 7 obj2['b'] 7 obj2[['c', 'd']] c 3 d 4 dtype: int64 obj2['d':'a'] d 4 b 7 a -5 dtype: int64

使用numpy的风格,使用布尔数组过滤数据,标量计算或者应用数学函数:

obj2[obj2>0] d 4 b 7 c 3 dtype: int64 obj2 * 2 d 8 b 14 a -10 c 6 dtype: int64 np.exp(obj2) d 54.598150 b 1096.633158 a 0.006738 c 20.085537 dtype: float64

从另一个角度看Series,可以认为它是一个长度固定且有序的字典,因为它将索引值和数据按位置配对。我们看:

'a' in obj2 # 像极了 key in dict True

我们很自然可以使用字典(json)数据创建Series:

sdata = {'Ohio':35000, 'Texas':71000, 'Oregon':16000, 'Utah':5000} obj3 = pd.Series(sdata) obj3 Ohio 35000 Texas 71000 Oregon 16000 Utah 5000 dtype: int64

指定字典数据的索引顺序:

states = ['California', 'Ohio', 'Oregon', 'Texas'] obj4 = pd.Series(sdata, index=states) obj4 California NaN Ohio 35000.0 Oregon 16000.0 Texas 71000.0 dtype: float64

指定索引中未包含的值(California)用NaN填充,不包含在索引中的(Utah)被排除在Series外。

使用缺失和NA表示缺失值,pandas中使用isnull和notnull函数来检查缺失数据。

pd.isnull(obj4) California True Ohio False Oregon False Texas False dtype: bool obj4.isnull() California True Ohio False Oregon False Texas False dtype: bool obj4.notnull() California False Ohio True Oregon True Texas True dtype: bool

Series自动对齐特性(依据index):

obj3 Ohio 35000 Texas 71000 Oregon 16000 Utah 5000 dtype: int64 obj4 California NaN Ohio 35000.0 Oregon 16000.0 Texas 71000.0 dtype: float64 obj3 + obj4 California NaN Ohio 70000.0 Oregon 32000.0 Texas 142000.0 Utah NaN dtype: float64

Series对象本身和其索引都有name属性:

obj4.name = 'population' obj4.index.name = 'states' obj4 states California NaN Ohio 35000.0 Oregon 16000.0 Texas 71000.0 Name: population, dtype: float64

修改Series的索引:

obj 0 4 1 7 2 -5 3 3 dtype: int64 obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan'] obj Bob 4 Steve 7 Jeff -5 Ryan 3 dtype: int64

DataFrame

DataFrame表示的是矩阵的数据表,包含已排序的列集合,每一列可以是不同的值类型(数值,字符串,布尔值等)。DataFrame既有行索引也有列索引。可以被视为一个共享相同索引(行索引)的Series的字典。 尽管DataFrame是二维的,但是可以利用分层索引在DataFrame中展示更高维度的数据。


有很多方式可以构建DataFrame,其中最常见的方式是利用包含长度列表或numpy数组的字典来实现:

data = {'state':['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 'year':[2000, 2001, 2002, 2001, 2002, 2003], 'pop':[1.5, 1.7, 3.6, 2.4, 2.9, 3.2]} frame = pd.DataFrame(data) frame stateyearpop0Ohio20001.51Ohio20011.72Ohio20023.63Nevada20012.44Nevada20022.95Nevada20033.2 frame.head() # 对于大数据集可以用来只展示前5行 stateyearpop0Ohio20001.51Ohio20011.72Ohio20023.63Nevada20012.44Nevada20022.9

指定特定的列顺序排列:

pd.DataFrame(data, columns=['year', 'state', 'pop']) yearstatepop02000Ohio1.512001Ohio1.722002Ohio3.632001Nevada2.442002Nevada2.952003Nevada3.2

如果传递的值不在字典中,会用NaN填充:

frame2 = pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'], index=['one', 'two', 'three', 'four', 'five', 'six']) frame2 yearstatepopdebtone2000Ohio1.5NaNtwo2001Ohio1.7NaNthree2002Ohio3.6NaNfour2001Nevada2.4NaNfive2002Nevada2.9NaNsix2003Nevada3.2NaN frame2.columns Index(['year', 'state', 'pop', 'debt'], dtype='object')

检索列数据:

frame2['state'] one Ohio two Ohio three Ohio four Nevada five Nevada six Nevada Name: state, dtype: object frame2.year #由于中文数据,个人用的比较少 one 2000 two 2001 three 2002 four 2001 five 2002 six 2003 Name: year, dtype: int64

返回的Series与原DataFrame有相同的索引,且Series的name属性也会被合理设置。

争对特殊属性用loc进行选取:

frame2.loc['three'] year 2002 state Ohio pop 3.6 debt NaN Name: three, dtype: object

列的引用可以用来修改数据:

frame2['debt'] = 16.5 frame2 yearstatepopdebtone2000Ohio1.516.5two2001Ohio1.716.5three2002Ohio3.616.5four2001Nevada2.416.5five2002Nevada2.916.5six2003Nevada3.216.5 frame2['debt'] = np.arange(6.) frame2 yearstatepopdebtone2000Ohio1.50.0two2001Ohio1.71.0three2002Ohio3.62.0four2001Nevada2.43.0five2002Nevada2.94.0six2003Nevada3.25.0

通过Series来赋值一列数据:

Series的索引会按照DataFrame的索引重新排列,并且在空缺的地方填充缺失值。

val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five']) frame2['debt'] = val frame2 yearstatepopdebtone2000Ohio1.5NaNtwo2001Ohio1.7-1.2three2002Ohio3.6NaNfour2001Nevada2.4-1.5five2002Nevada2.9-1.7six2003Nevada3.2NaN

如果被赋值的列不存在,则会生成一个新的列。del关键字也能删除DataFrame的列。

frame2['eastern'] = frame2['state'] == 'Ohio' frame2 yearstatepopdebteasternone2000Ohio1.5NaNTruetwo2001Ohio1.7-1.2Truethree2002Ohio3.6NaNTruefour2001Nevada2.4-1.5Falsefive2002Nevada2.9-1.7Falsesix2003Nevada3.2NaNFalse

frame2.eastern不能创建新列。所以个人推荐frame2[‘eastern’]。

del frame2['eastern'] frame2.columns Index(['year', 'state', 'pop', 'debt'], dtype='object')

DataFrame中选取的列是数据的视图,而不是拷贝。因此,对Series的修改会隐射到DataFrame中。如果需要复制,应当使用Series的copy方法。

通过嵌套字典来创建DataFrame:

pop = {'Nevada':{2001: 2.4, 2002: 2.9}, 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}} frame3 = pd.DataFrame(pop) frame3 NevadaOhio2000NaN1.520012.41.720022.93.6

内部字典的键被联合,如果已经显示指明索引的话,内部字典的键将不会被排序:

pd.DataFrame(pop, index=pd.Index([2001, 2002, 2003])) # pd.DataFrame(pop, index=[2001, 2002, 2003])令人困惑的失败了--- NevadaOhio20012.41.720022.93.62003NaNNaN

包含Series的字典也可以创建DataFrame:

pdata = {'Ohio': frame3['Ohio'][:-1], 'Nevada': frame3['Nevada'][:2]} pd.DataFrame(pdata) OhioNevada20001.5NaN20011.72.4

转置DataFrame:

frame3.T 200020012002NevadaNaN2.42.9Ohio1.51.73.6

为Frame添加name属性:

frame3.index.name = 'year' frame3.columns.name = 'state' frame3 stateNevadaOhioyear2000NaN1.520012.41.720022.93.6

DataFrame的values会以二维的ndarray形式返回:

frame3.values array([[nan, 1.5], [2.4, 1.7], [2.9, 3.6]])

如果DataFrame有不同数据类型的列(大多数情况),values会自动选择适合所有列的类型(一般是object):

frame2.values array([[2000, 'Ohio', 1.5, nan], [2001, 'Ohio', 1.7, -1.2], [2002, 'Ohio', 3.6, nan], [2001, 'Nevada', 2.4, -1.5], [2002, 'Nevada', 2.9, -1.7], [2003, 'Nevada', 3.2, nan]], dtype=object) 构造DataFrame的有效输入: 类型注释2D ndarray数据的矩阵,行和列的标签是可选参数数组,列表和元组构成的字典每个序列为DataFrame的一列,所有序列必须长度相等numpy结构化/记录化的数组与数组构成字典一致Series构成的字典每个值为一列,每个Series的索引联合组成行索引,也可以显示传递索引字典构成的字典每个内部字典成一列,内部字典的键联合成行索引字典或Series构成的列表列表中的一个元素形成DataFrame的一行,字典键或Series的索引联合形成DataFrame的列标签列表或元组构成的列表与2D ndarray一致其他DataFrame如果不显示传递索引,则会使用原DataFrame的索引numpy MaskedArray与2D ndarray的情况类似,但隐蔽值会在DataFrame中成为NA/缺失值 data = np.random.randn(3, 4) data array([[-0.72464584, -1.23894813, 0.33468619, 0.97633599], [-1.5455614 , -0.71447204, -0.57767409, 0.08786041], [ 1.86983296, -2.29198061, 0.98661309, 1.19946911]]) pd.DataFrame(np.where(data>0, data, np.nan)) # 实现maskedArray--个人想法 01230NaNNaN0.3346860.9763361NaNNaNNaN0.08786021.869833NaN0.9866131.199469

索引对象

pandas中的索引对象是用于存储轴标签和其他元数据的(例如轴名称)。构造Series或DataFrame时,所使用的任意数组或标签序列都可以在内部转换成索引对象:

obj = pd.Series(range(3), index=['a', 'b', 'c']) index = obj.index index Index(['a', 'b', 'c'], dtype='object') index[1:] Index(['b', 'c'], dtype='object')

index是不可修改的:

index[1] = 'd' # TypeError --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-4-a452e55ce13b> in <module>() ----> 1 index[1] = 'd' c:\users\sandwich\appdata\local\programs\python\python36\lib\site-packages\pandas\core\indexes\base.py in __setitem__(self, key, value) 2049 2050 def __setitem__(self, key, value): -> 2051 raise TypeError("Index does not support mutable operations") 2052 2053 def __getitem__(self, key): TypeError: Index does not support mutable operations

不变性利于多种数据共享索引对象。

labels = pd.Index(range(3)) labels RangeIndex(start=0, stop=3, step=1) obj2 = pd.Series([1.5, -2.5, 0], index=labels) obj2 0 1.5 1 -2.5 2 0.0 dtype: float64 obj2.index is labels True

除了类似数组,索引对象也是一个固定大小的集合:

frame3 stateNevadaOhioyear2000NaN1.520012.41.720022.93.6 frame3.columns Index(['Nevada', 'Ohio'], dtype='object', name='state') 'Ohio' in frame3.columns True

与python集合不同,pandas索引对象可以包含重复标签:

dup_labels = pd.Index(['foo', 'foo', 'bar', 'bar']) dup_labels Index(['foo', 'foo', 'bar', 'bar'], dtype='object') 一些索引对象的方法和属性: 方法描述append将额外的索引对象粘贴到原索引后,产生一个新的索引difference计算两个索引的差集intersection计算两个索引的交集union计算两个索引的并集isin计算表示每一个值是否在传值容器中的布尔数组delete将位置i的元素删除,产生一个新的索引drop根据传参删除指定索引值,产生一个新的索引insert在i位置插入元素,产生一个新的索引is_monotonic如果索引序列递增返回Trueis_unique如果索引序列唯一则返回Trueunique计算索引的唯一值序列duplicated放回是否重复的布尔数组

基本功能

了解Series,DataFrame基本交互机制,深入部分后面再说。详情可以参考官方文档。

重建索引(索引重置顺序不修改对应关系)

reindex是pandas对象的重要方法,该方法用于创建一个符合新索引的对象:

obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=list('dbac')) obj d 4.5 b 7.2 a -5.3 c 3.6 dtype: float64

reindex方法会将数据按照新索引重新排列,不存在的索引值会引入NA:

obj2 = obj.reindex(list('abcde')) obj2 a -5.3 b 7.2 c 3.6 d 4.5 e NaN dtype: float64

对于顺序数据,尤其是时间数据,重建索引时会需要进行插值或填值,可通过method参数来指定。例如ffill,会根据向前填充。

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

DataFrame中,reindx可以修改行索引,列索引,也可以同时修改两者。传递一个列参数时默认重置行索引:

frame = pd.DataFrame(np.arange(9).reshape(3, 3), index=list('acd'), columns=['Ohio', 'Texas', 'California']) frame OhioTexasCaliforniaa012c345d678 frame2 = frame.reindex(list('abcd')) frame2 OhioTexasCaliforniaa0.01.02.0bNaNNaNNaNc3.04.05.0d6.07.08.0 states = ['Texas', 'Utah', 'California'] frame.reindex(columns=states) TexasUtahCaliforniaa1NaN2c4NaN5d7NaN8

很多时候我们更倾向于使用loc来实现:

frame.loc[list('abcd'), states] # 看来将来就不能用了 c:\users\sandwich\appdata\local\programs\python\python36\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: Passing list-likes to .loc or [] with any missing label will raise KeyError in the future, you can use .reindex() as an alternative. See the documentation here: https://pandas.pydata.org/pandas-docs/stable/indexing.html#deprecate-loc-reindex-listlike """Entry point for launching an IPython kernel. TexasUtahCaliforniaa1.0NaN2.0bNaNNaNNaNc4.0NaN5.0d7.0NaN8.0 reindex方法参数: 参数描述index新建作为索引的序列,可以是索引实例或任意其他序列类型Python数据结构,索引使用时无需复制可使用变量传递method插值方式,ffill向前填充,bfill向后填充fill_value通过重新索引引入缺失数据时使用的代替值limit当向前或向后填充时,所需填充的最大尺寸间隙(元素个数)tolerance向前或向后填充时,所需填充的不确定匹配下的最大尺寸间隙(以绝对数字距离)level匹配MultiIndex级别的简单索引,否在选择子集copy如果为True,即使新索引等于旧索引,也总是复制底层数据,如果为False则索引相同也不要复制数据 frame OhioTexasCaliforniaa012c345d678 view = frame.reindex(list('adc'), copy=False) view.loc['a', 'Ohio'] = 99 view OhioTexasCaliforniaa9912d678c345 frame OhioTexasCaliforniaa012c345d678

感觉copy=False的时候也是拷贝数据,没有提供原数据的视图,官方说明也不清楚,不影响使用总感觉copy参数比较多余。

轴向上删除条目

如果已经有了索引数组不含条目的列表,reindex就可以删除不需要的条目。但drop方法提供单独删除的便捷。

obj = pd.Series(np.arange(5.), index=list('abcde')) obj a 0.0 b 1.0 c 2.0 d 3.0 e 4.0 dtype: float64 new_obj = obj.drop('c') new_obj a 0.0 b 1.0 d 3.0 e 4.0 dtype: float64 obj.drop(['d', 'c']) a 0.0 b 1.0 e 4.0 dtype: float64

DataFrame中可以行列轴向上删除:

data = pd.DataFrame(np.arange(16).reshape((4, 4)), index=['Ohio', 'Colorado', 'Utha', 'New York'], columns=['one', 'two', 'three', 'four']) data onetwothreefourOhio0123Colorado4567Utha891011New York12131415 data.drop(['Colorado', 'Ohio']) onetwothreefourUtha891011New York12131415 data.drop('one', axis=1) # 也可以通过axis='columns'来实现 twothreefourOhio123Colorado567Utha91011New York131415

很多函数,例如drop会修改DataFrame的尺寸或形状,这些操作一般不会修改原数据,但是我们有的时候希望操作原数据而不是重新赋值时可以使用inplace参数来指定。

data.drop('Utha', inplace=True) # 原数组改变不返回结果 data #原数据被修改了 onetwothreefourOhio0123Colorado4567New York12131415

索引、选择与过滤

与之前numpy中索引和切片类似,需要结合布尔数组(Series)位操作等待,统一适用的原则较少,需要自己慢慢修行。

obj = pd.Series(np.arange(4.), index=list('abcd')) obj a 0.0 b 1.0 c 2.0 d 3.0 dtype: float64 obj['c'] #单个是值 2.0 obj[2:4] #多个是Series[切片] c 2.0 d 3.0 dtype: float64 obj[[1, 3]] b 1.0 d 3.0 dtype: float64 obj[['b', 'a', 'd']] b 1.0 a 0.0 d 3.0 dtype: float64 obj['b':'c'] #区别数值切片,右边也取,个人比较喜欢这样切片不要数数 b 1.0 c 2.0 dtype: float64 obj[obj<2] #布尔索引-obj<2生成布尔Series a 0.0 b 1.0 dtype: float64 obj['b':'c'] = 5 # 索引切片修改,原生修改 obj a 0.0 b 5.0 c 5.0 d 3.0 dtype: float64

DataFrame的索引相对也复杂些:

data = pd.DataFrame(np.arange(16).reshape((4, 4)), index=['Ohio', 'Colorado', 'Utha', 'New York'], columns=['one', 'two', 'three', 'four']) data onetwothreefourOhio0123Colorado4567Utha891011New York12131415 data['two'] # 等价data.two(少用),属性作用在列上 Ohio 1 Colorado 5 Utha 9 New York 13 Name: two, dtype: int32 data[['three', 'one']] threeoneOhio20Colorado64Utha108New York1412 data[:2] # []另一种,数值和布尔体现在行上,尤其是布尔很实用。后续将 onetwothreefourOhio0123Colorado4567 data[data['three']>5] #布尔很实用,加上位逻辑操作可以说是神技 onetwothreefourColorado4567Utha891011New York12131415

拆解下布尔的机理:

data['three']>5 #返回了一个布尔Series,带Index的。这里可以利用index的对应特性来实现筛选数据 Ohio False Colorado True Utha True New York True Name: three, dtype: bool logic = data['three']>5 data[logic] onetwothreefourColorado4567Utha891011New York12131415

布尔的另一个常用机制:

data < 5 onetwothreefourOhioTrueTrueTrueTrueColoradoTrueFalseFalseFalseUthaFalseFalseFalseFalseNew YorkFalseFalseFalseFalse data[data<5] = 0 data onetwothreefourOhio0000Colorado0567Utha891011New York12131415

这个例子和numpy二维数组异曲同工, 阈值处理十分常用!

arr = np.random.randn(4, 4) arr array([[-0.4242416 , -0.28119606, 0.18744757, -0.26214791], [-0.28362239, -0.82234463, -2.21274359, 0.77390936], [-3.23527271, -0.35721109, -0.40062906, 0.95512268], [ 0.2237259 , -0.35128217, 0.98827603, -1.01600844]]) arr[arr<0] = 0 arr array([[0. , 0. , 0.18744757, 0. ], [0. , 0. , 0. , 0.77390936], [0. , 0. , 0. , 0.95512268], [0.2237259 , 0. , 0.98827603, 0. ]])

使用loc和iloc选择数据

针对DataFrame在行上的标签索引,使用loc符号和iloc符号。loc(显示定义的标签),iloc(默认自动生成的数值标签,绝对位置),但是维度上都满足【axis0, axis1】

data.loc['Colorado', ['two', 'three']] two 5 three 6 Name: Colorado, dtype: int32 data.iloc[2, [3, 0, 1]] four 11 one 8 two 9 Name: Utha, dtype: int32 data.iloc[2] one 8 two 9 three 10 four 11 Name: Utha, dtype: int32 data.iloc[[1, 2], [3, 0, 1]] fouronetwoColorado705Utha1189

DataFrame切片:

data.loc[:'Utha', 'two'] Ohio 0 Colorado 5 Utha 9 Name: two, dtype: int32 data.iloc[:, :3][data.three>5] # 可以说比较花哨了,可以分步骤实现 onetwothreeColorado056Utha8910New York121314 DataFrame索引选项: 类型描述df[val]从DataFrame中选择单列或列序列;特殊情况的便利:布尔数组过滤行,对行切片或布尔逻辑处理数值数组作用列;切片、布尔作用行df.loc[val]根据标签选择单行还是多行df.loc[val1, val2]根据标签同时选择行和列中的一部分df.iloc[where]根据整数位置选择单行或者多行df.iloc[:, where]根据整数位置选择单列或者多列df.iloc[where_i, where_j]根据位置选择行和列df.at[label_i, label_j]根据行、列标签选择单个标量值df.iat[i, j]根据行、列整数位置选择单个标量值reindex方法通过标签选择行或列get_value, set_value方法根据行和列的标签获取和设置单个值

整数索引

在pandas上使用整数索引会带来歧义,与python内建索引的方式存在不同:

ser = pd.Series(np.arange(3.)) ser[-1] # 这样会把-1当作索引值,但是索引中没有-1 --------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-3-58a209de0185> in <module>() 1 ser = pd.Series(np.arange(3.)) ----> 2 ser[-1] c:\users\administrator\appdata\local\programs\python\python37\lib\site-packages\pandas\core\series.py in __getitem__(self, key) 765 key = com._apply_if_callable(key, self) 766 try: --> 767 result = self.index.get_value(self, key) 768 769 if not is_scalar(result): c:\users\administrator\appdata\local\programs\python\python37\lib\site-packages\pandas\core\indexes\base.py in get_value(self, series, key) 3116 try: 3117 return self._engine.get_value(s, k, -> 3118 tz=getattr(series.dtype, 'tz', None)) 3119 except KeyError as e1: 3120 if len(self) > 0 and self.inferred_type in ['integer', 'boolean']: pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_value() pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_value() pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item() pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item() KeyError: -1 ser.iloc[-1] # 只能在这儿告诉索引用的是位置不是索引值。 2.0

这部分是个人的理解!

如果显示定义了index就不会存在这样的困扰:

ser = pd.Series(np.arange(3.), index=list('abc')) ser a 0.0 b 1.0 c 2.0 dtype: float64 ser[-1] # 这里索引是非整数形式,自动转化成位置索引。 2.0

在不指定loc或iloc时,内建机制会自动判断。我们不妨测试自定义整数索引虽然大多数人不会这么无聊。

ser = pd.Series(np.arange(3.), index=list(range(1, 4))) ser 1 0.0 2 1.0 3 2.0 dtype: float64 ser[-1] # 按照理解索引定义是整数,会找-1索引值会报错 --------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-9-16980b1a6c97> in <module>() ----> 1 ser[-1] # 按照理解索引定义是整数,会找-1索引值会报错 c:\users\administrator\appdata\local\programs\python\python37\lib\site-packages\pandas\core\series.py in __getitem__(self, key) 765 key = com._apply_if_callable(key, self) 766 try: --> 767 result = self.index.get_value(self, key) 768 769 if not is_scalar(result): c:\users\administrator\appdata\local\programs\python\python37\lib\site-packages\pandas\core\indexes\base.py in get_value(self, series, key) 3116 try: 3117 return self._engine.get_value(s, k, -> 3118 tz=getattr(series.dtype, 'tz', None)) 3119 except KeyError as e1: 3120 if len(self) > 0 and self.inferred_type in ['integer', 'boolean']: pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_value() pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_value() pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item() pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.Int64HashTable.get_item() KeyError: -1

所以开发者建议使用整数索引是保持使用loc,如果显示索引也是整数时可以使用iloc,个人建议统一使用iloc

ser.iloc[:2] 1 0.0 2 1.0 dtype: float64

算术和数据对齐

不同索引的对象之间的算术行为为pandas提供了应用的重要特性。当对象相加时,如果存在某个索引对不相同,则返回两个对象的并集。索引标签的自动外连接(outer join)

s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=list('acde')) s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], index=list('acefg')) s1 a 7.3 c -2.5 d 3.4 e 1.5 dtype: float64 s2 a -2.1 c 3.6 e -1.5 f 4.0 g 3.1 dtype: float64 s1 + s2 a 5.2 c 1.1 d NaN e 0.0 f NaN g NaN dtype: float64

完成步骤:

按照索引的并集补全数据 s1=[7.3, -2.5, 3.4, 1.5, NaN, NaN], s2=[-2.1, 3.6, NaN, -1.5, 4, 3.1] 补全后数据相加NaN任何数学操作都是NaN pd.Series.add(s1, s2, fill_value=0) # 我知道你们怎么想的,设计师也知道 a 5.2 c 1.1 d 3.4 e 0.0 f 4.0 g 3.1 dtype: float64

在DataFrame中,行和列都会执行对齐:

df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'), index=['Ohio', 'Texas', 'Colorado']) df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon']) df1 bcdOhio0.01.02.0Texas3.04.05.0Colorado6.07.08.0 df2 bdeUtah0.01.02.0Ohio3.04.05.0Texas6.07.08.0Oregon9.010.011.0 df1 + df2 bcdeColoradoNaNNaNNaNNaNOhio3.0NaN6.0NaNOregonNaNNaNNaNNaNTexas9.0NaN12.0NaNUtahNaNNaNNaNNaN

行列索引都是并集作用的结果,c e两个不是共有列所有全为NaN,所以两个行列完全不同的DataFrame得到全空。 个人理解的设计哲学,一个序列(数组,传递参数等),位置和名称知其一即可。位置必须按顺序来,有了名称顺序就不必要了。 这里的索引是按名称对应的,所以顺序无关紧要。

使用填充值得算术方法

类似上面Series的实现方法,DataFrame中也存在,索引不完全匹配希望0填充的情况:

df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)), columns=list('abcd')) df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)), columns=list('abcde')) df1 abcd00.01.02.03.014.05.06.07.028.09.010.011.0 df2 abcde00.01.02.03.04.015.06.07.08.09.0210.011.012.013.014.0315.016.017.018.019.0 df1 + df2 abcde00.02.04.06.0NaN19.011.013.015.0NaN218.020.022.024.0NaN3NaNNaNNaNNaNNaN

fill_value:

df1.add(df2, fill_value=0) abcde00.02.04.06.04.019.011.013.015.09.0218.020.022.024.014.0315.016.017.018.019.0

不仅这里可以使用填充值,reindex等也适用:

df1.reindex(columns=df2.columns, fill_value=0) abcde00.01.02.03.0014.05.06.07.0028.09.010.011.00 灵活算术方法: 方法描述add, radd加法(+)sub, rsub减法(-)div, rdiv除法(/)floordiv, rfloordiv整除(//)mul, rmul乘法(*)pow, rpow幂次方(^/**)

r开头的含义是参数的翻转:

1 / df1 等价于 df1.rdiv(1)

DataFrame和Series之间的操作

DataFrame和Series的操作类似numpy不同维度数组间的操作(广播),示例如下:

arr = np.arange(12.).reshape((3, 4)) arr array([[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.]]) arr[0] array([0., 1., 2., 3.]) arr - arr[0] # 广播作用到每一行上 array([[0., 0., 0., 0.], [4., 4., 4., 4.], [8., 8., 8., 8.]])

DataFrame中类似的实现:

frame = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon']) frame bdeUtah0.01.02.0Ohio3.04.05.0Texas6.07.08.0Oregon9.010.011.0 series = frame.iloc[0] series b 0.0 d 1.0 e 2.0 Name: Utah, dtype: float64 frame - series bdeUtah0.00.00.0Ohio3.03.03.0Texas6.06.06.0Oregon9.09.09.0

对于不同索引的DataFrame和Series之间的算术计算,会按照索引并集计算:

series2 = pd.Series(np.arange(3.), index=list('bef')) frame - series2 bdefUtah0.0NaN1.0NaNOhio3.0NaN4.0NaNTexas6.0NaN7.0NaNOregon9.0NaN10.0NaN

列上进行广播操作:

series3 = frame['d'] series3 Utah 1.0 Ohio 4.0 Texas 7.0 Oregon 10.0 Name: d, dtype: float64 frame - series3 # 很遗憾,算术符号只能在行上广播 OhioOregonTexasUtahbdeUtahNaNNaNNaNNaNNaNNaNNaNOhioNaNNaNNaNNaNNaNNaNNaNTexasNaNNaNNaNNaNNaNNaNNaNOregonNaNNaNNaNNaNNaNNaNNaN

我们只能通过函数操作, axis指定匹配轴:

frame.sub(series3, axis=0) bdeUtah-1.00.01.0Ohio-1.00.01.0Texas-1.00.01.0Oregon-1.00.01.0

函数应用和映射

numpy通用函数逐元素数组方法对pandas也适用:

frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon']) frame bdeUtah-0.828696-0.1253560.871033Ohio-0.345832-2.1663381.577610Texas-0.818417-0.2569081.469507Oregon-1.642177-2.4172740.435113 np.abs(frame) bdeUtah0.8286960.1253560.871033Ohio0.3458322.1663381.577610Texas0.8184170.2569081.469507Oregon1.6421772.4172740.435113

常用操作:将函数作用到一行或一列上,apply

f = lambda x: x.max() - x.min() frame.apply(f) # 沿着行方向,作用在列上 b 1.296346 d 2.291918 e 1.142497 dtype: float64 frame.apply(f, axis=1) # 作用在行上 Utah 1.699729 Ohio 3.743948 Texas 2.287924 Oregon 2.852387 dtype: float64

很多时候,mean()和sum()作为DataFrame的自带统计方法,不必使用apply实现。

apply的返回结果也可以是多个值的Series:

def f(x): return pd.Series([x.min(), x.max()], index=['min', 'max']) frame.apply(f) bdemin-1.642177-2.4172740.435113max-0.345832-0.1253561.577610

逐元素函数,applymap

将每个元素格式化成字符串:

_format = lambda x: '%.2f' % x frame.applymap(_format) bdeUtah-0.83-0.130.87Ohio-0.35-2.171.58Texas-0.82-0.261.47Oregon-1.64-2.420.44 _.b # 注意object类型,转化成功 Utah -0.83 Ohio -0.35 Texas -0.82 Oregon -1.64 Name: b, dtype: object

Series的映射,map:

frame['e'].map(_format) Utah 0.87 Ohio 1.58 Texas 1.47 Oregon 0.44 Name: e, dtype: object

排序和排名

按照行列索引排序sort_index:

obj = pd.Series(range(4), index=list('dabc')) obj.sort_index() a 1 b 2 c 3 d 0 dtype: int64 不同轴向上索引排序: frame = pd.DataFrame(np.arange(8).reshape(2, 4), index=['three', 'one'], columns=list('dabc')) frame dabcthree0123one4567 frame.sort_index(axis=1) abcdthree1230one5674 frame.sort_index(axis=1, ascending=False) # 按列索引排序,不升序 dcbathree0321one4765

原生python中sorted方法的key的设计思想在sort_index得到体现(by参数), 竟然强烈不建议,可能是设计上的考虑吧,建议用.sort_values(index.map)实现!!! 值得一提的是datetime的index是可以排序的。

def key(val): index=['one', 'three'] return index.index(val) # frame.sort_index(axis=0, by=key) frame['key'] = frame.index.map(key) frame.sort_values('key').drop('key', axis=1) dabcone4567three0123

sort_values,值排序:

obj = pd.Series([4, 7, -3, 2]) obj.sort_values() 2 -3 3 2 0 4 1 7 dtype: int64 obj = pd.Series([4, np.nan, 7, np.nan, -3, 2]) #nan会放在最后,可以通过na_position指定 obj.sort_values() 4 -3.0 5 2.0 0 4.0 2 7.0 1 NaN 3 NaN dtype: float64

DataFrame中多列标准排序:

frame = pd.DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]}) frame ba0401712-30321 frame.sort_values(by='b') #by可缺省 ba2-30321040171 frame.sort_values(['a', 'b']) #先a, 后b ba2-30040321171

rank排名是对数组从1到有效数据点总数分配名次的操作:

obj = pd.Series([7, -5, 7, 4, 2, 0, 4]) obj.rank() 0 6.5 1 1.0 2 6.5 3 4.5 4 3.0 5 2.0 6 4.5 dtype: float64

排名根据数据中观察顺序进行分配:

obj.rank(method='first') 0 6.0 1 1.0 2 7.0 3 4.0 4 3.0 5 2.0 6 5.0 dtype: float64

0, 2号的数值排名变成6和7,不是6.5,因为数据标签0在2的前面。

可以降序排名:

# 将值分给数组中的最大排名 obj.rank(ascending=False, method='max') 0 2.0 1 7.0 2 2.0 3 4.0 4 5.0 5 6.0 6 4.0 dtype: float64

DataFrame指定行列排名:

frame = pd.DataFrame({'b':[4.3, 7, -3, 2], 'a':[0, 1, 0, 1], 'c':[-2, 5, 8, -2.5]}) frame bac04.30-2.017.015.02-3.008.032.01-2.5 frame.rank(axis='columns') # axis=1等效 bac03.02.01.013.01.02.021.02.03.033.02.01.0 排名中打破评级的方法: 方法描述average默认方法,每个组中分配平均排名min对整个数组使用最小排名max对整个数组使用最大排名first按照值在数组中出现的次序分配排名dense类似于min但是排名间总是加1,而不是一个数组中相等元素的数量

含有重复标签的轴索引

索引不重复这个现象是普遍的,而且reindex也默认要求索引的标签是唯一的(非强制),我们考虑下带有重复索引的Series:

obj = pd.Series(range(5), index=list('aabbc')) obj a 0 a 1 b 2 b 3 c 4 dtype: int64

is_unique属性检查唯一性:

obj.index.is_unique False obj.index.duplicated() # 指出重复索引的位置 array([False, True, False, True, False])

重复索引带来的索引问题:

obj['a'] a 0 a 1 dtype: int64 obj['c'] 4

这样可能会给代码带来更高的复杂性,由于索引带来的不同结果。

DataFrame中也是如此:

df = pd.DataFrame(np.random.randn(4, 3), index=list('aabb')) df 012a-0.6484920.556554-0.397103a-1.574263-0.301252-1.105041b-1.013025-0.817644-0.725848b1.073580-0.8869480.323123 df.loc['b'] 012b-1.013025-0.817644-0.725848b1.073580-0.8869480.323123

描述性统计的概述与计算

pandas及其对象装配了常用的数学、统计方法的集合。其中大部分属于归约或汇总统计的类别,DataFrame的行列中抽取一个Series或一系列的单个值(如总和,平均值)。与numpy不同,内建了处理缺失值的能力。

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']) df onetwoa1.40NaNb7.10-4.5cNaNNaNd0.75-1.3

df.sum()方法返回在列上加和的Series:

df.sum() one 9.25 two -5.80 dtype: float64

axis指定加和的纬度(实现按列方向每行求和):

df.sum(axis=1) # axis='columns' a 1.40 b 2.60 c 0.00 d -0.55 dtype: float64 df.sum(axis=1, min_count=1) a 1.40 b 2.60 c NaN d -0.55 dtype: float64

版本跟新带来了问题nan+nan=0,个人感觉这个更新带来的效果很差, 好在可以使用min_count=1来解决。

如果我们不需要越过NA,可以设置skipna为false:

df.mean(axis=1, skipna=False) a NaN b 1.300 c NaN d -0.275 dtype: float64 归约方法可选参数: 方法描述axis归约轴,0为行向,1为列向skipna排除缺失值,默认Truelevel针对多层索引(MultiIndex),可以缩减分组的层次

统计方法:

df.idxmax() one b two d dtype: object

积累型方法:

df.cumsum() onetwoa1.40NaNb8.50-4.5cNaNNaNd9.25-5.8

统计描述:

df.describe() onetwocount3.0000002.000000mean3.083333-2.900000std3.4936852.262742min0.750000-4.50000025%1.075000-3.70000050%1.400000-2.90000075%4.250000-2.100000max7.100000-1.300000 描述性统计和汇总统计: 方法描述count非NA值的个数describe计算Series或DataFrame各列的汇总统计集合min, max计算最小,最大值argmin, argmax计算最小最大值索引位置idmin, idmax计算最小最大值索引标签quantile计算样本从0-1间的分位数sum加和mean均值median中位数(50%分位数)mad平均值的平均绝对偏差prod所有值的积var值的样本方差std值的样本标准差skew样本偏度(第三刻度)值kurt样本峰度(第四刻度)值cumsum累计值cummin, cummax累计值的最小值,最大值cumprod值得累计积diff计算第一个算术差值(对时间序列有用)pct_change计算百分比变化序列

相关性和协方差

利用第三方书籍来源pandas-datareader, 可以利用conda和pip来安装。

import pandas_datareader.data as web all_data = {ticker:web.get_data_yahoo(ticker) for ticker in ['AAPL', 'IBM', 'MSFT', 'GOOG']} price = pd.DataFrame({ticker:data['Adj Close'] for ticker, data in all_data.items()}) volume = pd.DataFrame({ticker: data['Volume'] for ticker, data in all_data.items()})

计算股价百分比:

returns = price.pct_change() returns.tail() AAPLIBMMSFTGOOGDate2019-10-240.001645-0.0023070.0196740.0014772019-10-250.0123160.0102190.0056450.0032832019-10-280.0100170.0039130.0245860.0196582019-10-29-0.023128-0.015812-0.009432-0.0212252019-10-30-0.0001230.0106860.012462-0.001053 示例pct_change(): df = pd.DataFrame(np.arange(12).reshape(3, 4)) df 012300123145672891011 df.diff() / pd.concat([pd.DataFrame([[np.nan] * 4]), df.iloc[:-1]], ignore_index=True) 01230NaNNaNNaNNaN1inf4.02.0000001.33333321.0000000.80.6666670.571429 df.pct_change() 01230NaNNaNNaNNaN1inf4.02.0000001.33333321.0000000.80.6666670.571429

在数学上实现pct.change()的计算方式,注意边界(逐个变化需要注意的是上边界) 继续对returns数据展开讨论:


Series的corr讨论的是两个Series中重叠的,非NA的,按索引对齐的相关性。相应的cov计算的是协方差。

returns['MSFT'].corr(returns['IBM']) # 计算相关性 0.486848267134005 returns['MSFT'].cov(returns['IBM']) # 计算协方差 9.400960332792436e-05

对于DataFrame存在喜闻乐见的相关性矩阵和协方差矩阵

returns.corr() AAPLIBMMSFTGOOGAAPL1.0000000.4063190.5723940.521399IBM0.4063191.0000000.4868480.413382MSFT0.5723940.4868481.0000000.655424GOOG0.5213990.4133820.6554241.000000 returns.cov() AAPLIBMMSFTGOOGAAPL0.0002470.0000830.0001330.000124IBM0.0000830.0001700.0000940.000082MSFT0.0001330.0000940.0002190.000147GOOG0.0001240.0000820.0001470.000231

corrwith()计算DataFrame每个行或列关于一个Series的相关性:

returns.corrwith(returns.IBM) AAPL 0.406319 IBM 1.000000 MSFT 0.486848 GOOG 0.413382 dtype: float64

当corrwith()传入DataFrame时,按照axis='columns’对齐,标签对齐。这里的数据我们可以计算出交易量百分比变化的相关性:

returns.corrwith(volume) AAPL -0.119807 IBM -0.132709 MSFT -0.085631 GOOG -0.007526 dtype: float64

唯一值,计数和成员属性

另一类相关的方法可以从一维Serie中包含的数据提取信息。参考下面的例子:

obj = pd.Series(list('cadaabbcc')) uniques = obj.unique() uniques # 后续可以使用.sort()排序 array(['c', 'a', 'd', 'b'], dtype=object) '''曾经我为了统计优良天用了collections.Counter,现在想来我就是拿着金碗要饭 没有鄙视Counter的意思''' obj.value_counts() # 我相信这个很常用!!!,默认计数由大到小排序 c 3 a 3 b 2 d 1 dtype: int64

pandas顶层同样设计了该方法:

pd.value_counts(obj, sort=False) d 1 a 3 b 2 c 3 dtype: int64

isin()用于过滤数据,获取掩码(布尔索引):

mask = obj.isin(['b', 'c']) mask 0 True 1 False 2 False 3 False 4 False 5 True 6 True 7 True 8 True dtype: bool obj[mask] # 使用过滤 0 c 5 b 6 b 7 c 8 c dtype: object

与isin()相关的Index.get_indexer():

提供一个索引数组,这个索引数组可以将可能非唯一值数组转化为另一个唯一值数组:

to_match = pd.Series(list('cabbca')) unique_vals = pd.Series(list('cba')) pd.Index(unique_vals).get_indexer(to_match) array([0, 2, 1, 1, 0, 2], dtype=int64) 唯一值、计数和集合成员属性方法: 方法描述isin计算表征Series中每个值是否包含在传入序列的布尔数组match计算数组中每个值的整数索引,形成一个唯一值数组。有助于数据对齐和join型的操作unique计算Series值中的唯一数组,按照观察顺序返回value_counts返回一个Series,索引是唯一值序列,值是计数个数,按照个数降序排序 pd.match(obj, list('bc')) # 还是少用这个吧 c:\users\sandwich\appdata\local\programs\python\python36\lib\site-packages\ipykernel_launcher.py:1: FutureWarning: pd.match() is deprecated and will be removed in a future version """Entry point for launching an IPython kernel. array([ 1, -1, -1, -1, -1, 0, 0, 1, 1], dtype=int64)

某些情况,我们需要计算DataFrame多个相关列的直方图,如下所示:

data = pd.DataFrame({'out1':[1, 3, 4, 3, 4], 'out2':[2, 3, 1, 2, 3], 'out3':[1, 5, 2, 4, 4]}) data out1out2out301211335241233244434 '''apply(get_series)这样的设计思路在很多地方都有使用''' result = data.apply(pd.value_counts).fillna(0) result out1out2out311.01.01.020.02.01.032.02.00.042.00.02.050.00.01.0 result.plot(kind='barh')

最新回复(0)