Pandas 是一款适用很广的数据处理的组件, 如果将来从事机械学习或者数据分析方面的工作, 咱们估计 70% 的时间都是在跟这个框架打交道. 那大家可能就有疑问了, 心想这个破玩意儿值得花 70% 的时间吗? 咱不是还有很牛逼的 Tensorflow, keras, 神经网络, classification 等等这些牛逼的技术 (词汇) 都没学习呢, 咋突然冒出来一个 pandas 就要在机器学习中占了大部分精力去处理呢? 其实啊, 同学们, 什么 TensorFlow, Keras, 神经网络, 随机森林啥的, 看起来牛气哄哄的高大上的词汇, 其实都是纸老虎, 那些大部分都是封装的的接口, 在实际应用的开发中, 基本都是固定模式, 主要就是调调参数而已 (真正的底层算法研究的除外哈), 当然这并不是你懒惰的理由, 你至少还是要了解算法原理的, 譬如: gradient descent, 求偏导等这些基本的概念咱们这些小白还是得有滴. 其实咱们在机器学习的应用开发中, 绝大部分是在做数据处理的工作, 因而数据处理工作的质量直接就关系到咱们整个应用的质量, 所以这是我们在机器学习中的重中之重, 请大家务必重视, 下面的每一行代码, 最好大家都要有实践才行. 因为 Pandas 的内容非常多, 所以这篇博文的篇幅会很长很长............ 哈哈, 大家有点耐心哈. 还有一点, 这一节的内容是后面 feature engineering(特征工程) 的基础, 你们如果有心要从事机器学习, 你们也必须要吃透这节内容的每一个知识点(很残酷的现实, 对不对,, 哈哈, 逃不掉的).
Dataframe 和 Series 的结构分析和创建
首先, pandas 中最重要的两个组成部分就是 Dataframe and series. 关于 Dataframe 咱们就可以把它看成一个 table(既有 row index 也有 column name 和 values, 其本质是一个字典 dictionary, 具体为什么, 要看下文的分析). 而 series 比 dataframe 的结构还要简单, 她其实就是只有一列数据, 而且他的这一列还是没有 column name 的, 她只有这一列的 values, 因而在结构上 series 只有 row index 和 values,series 的本质是一个 list, 具体为什么是 list, 也是看下面的创建过程. 好了, 那咱们先自定义一个 dataframe, 如下所示:
- #dataframe allows different index other than 0,1,2,3,4
- pd.DataFrame({
- 'A':[434,54],'B':[4,56]
- },index = [1,2])
咱们看上面创建的 dataframe 对象, 首先, index(相当于这个 table 的 row number)是可以自定义的, 你既可以从 0 开始(默认), 也可以从 100 开始, 甚至可以是 abc. 然后这个 dataframe 的 column name 分别是 "A" 和 "B", 这其实相当于这个字典的 key 值. 说到这里, 大家肯定已经理解了, 为什么我上面说 dataframe 的本质是一个字典了. 上面创建的这个 dataframe 的结果如下
- A B
- 1 434 4
- 2 54 56
那么下面我们来分析一下更加简单的 series 的结构吧, 首先咱们先创建一个 series 对象, 如下所示
- #create series with customerized index
- pd.Series([4,5,6,7,8,23,54], index=['A','B','C','D','E','F','G'])
和 dataframe 一样, series 也是可以自定义 index, 但是 series 没有 column name, 它只有 values, 因而可以看出, 它的本质是一个 list 结构. 她的返回结果如下
- A 4
- B 5
- C 6
- D 7
- E 8
- F 23
- G 54
- dtype: int64
可以很明显的看出它的结构. dataframe 和 series 是 pandas 的基础, 尤其是他们的结构, 一定要了然于胸, 这是 pandas 这个组合拳的基本功, 只有基本功扎实了, 才能继续学习更加灵活和瞬息万变的新招式.
读写数据
机器学习, 顾名思义就是让机器不断学习之前的经验和数据然后来做出预判. 那么问题来了, 我们如何把我们收集到的数据读到内存中来进行操作, 学过计算机的都知道, 计算机运算的时候是通过 CPU 对内存的数据的操作, 那么我们如何将硬盘上的数据, 例如: CSV, Excel 等等这些结构化的数据读入我们的内存, 并且转换成 dataframe 呢? 大家不用怕, pandas 已经将这一系列的 io 操作转换成一句代码就 OK 了, 执行调用下面的 API, 一切轻松搞定:
- #read a CSV data from locally
- wine_reviews = pd.read_csv("C:\\Users\\tangx\\OneDrive\\Desktop\\DATA\\winemag-data-130k-v2.csv")
- #grab the first 5 examples
- wine_reviews.head()
上面代码的第一句话就是讲本地文件读出来并且转成 dataframe 格式赋值给 wine_reviews, 由于实际中的数据往往非常多, 因而我们通常只截取前 5 条数据进行观察, 上面的第二行代码就是通过 dataframe.head()的方式提取前 5 条数据.
通过在 Spyder 中打开 wine_reviews 变量可以看出, 这个数据集一共有 129971 条数据, 每条数据有 14 个特征(feature). 通过观察上面的表格可以看出, 系统默认给这个 dataframe 加了一个从 0 开始的 index, 但是这张表本来的第一列也是从 0 开始并且递增的数字, 因此我们就像让这张表本来的第一列作为咱们的 index, 或者说是 row number, 咱们可以在加载数据的时候通过加一个参数实现, 这个参数就是 index_col
wine_reviews = pd.read_csv('C:\\Users\\tangx\\OneDrive\\Desktop\\DATA\\winemag-data-130k-v2.csv', index_col = 0)
因为数据的形式不止有 CSV, 也有譬如 Excel 等, 所以 pandas 在读取数据的时候不止有 read_csv(), 也有 read_excel()等等一大堆的 API 供大家选择.
在机器学习的应用开发的过程中, 写数据并不是一个常用的操作, 想想看也是, 你总不能把内存的数据写到磁盘中再去处理计算吧, 对吧? 但是呢, 技多不压身嘛, 咱就顺便把他学习了吧, 哈哈, 其实也简单的跟一一样一样的, 就一句代码搞定.
wine_reviews.head().to_csv("C:\\Users\\tangx\\OneDrive\\Desktop\\writedata.csv")
同理, 你也可以 to_excel()等等, 随便你. 上面这些就是一些最基础也是最常用的一些数据读写功能.
Indexing and selection
根据上面的结构分析, 咱们可以看出 dataframe 就是一个 table, 那么既然是 table, 在一些应用场景就肯定会有一些需求是获取某一个元素, 某一行或者某一列的数据, 那么这里就需要用到 pandas 里面的 index 和 selection 了. 首先, 咱们先介绍 2 中常用的 index 的方法, 他们分别是 dataframe.loc[] 和 dataframe.iloc[]. 注意这里有一个小细节, index 并不是函数方法, 咱们都是用的方括号[], 而不是括号(). 那么他们到底是什么意思呢? 咱们先看一下下面的代码, 咱们先随机创造一个 8*4 的 dataframe, 它的 index 和 column 分别是日期和["A","B","C","D"]. 代码如下:
- import numpy as np
- #help(pd.date_range)
- dates = pd.date_range('1/1/2000',periods = 8) #create date from 2000-01-01 to 2000-01-07
- df = pd.DataFrame(np.random.randn(8,4), index = dates, columns = ['A','B','C','D']) #assign index and columns to the dataframe
它的返回结果如下
- A B C D
- 2000-01-01 -1.148187 1.584064 -0.589693 -1.403843
- 2000-01-02 -1.310810 -0.920240 -2.752621 0.913722
- 2000-01-03 -0.049943 1.280664 -0.353257 -0.023290
- 2000-01-04 -0.359402 0.350923 -0.455901 -1.747723
- 2000-01-05 -0.880048 -0.780842 -0.351765 -1.596586
- 2000-01-06 1.106137 0.419967 -0.409990 -0.513611
- 2000-01-07 1.348941 1.557287 0.416174 -1.270166
现在我们就来瞧一瞧如何用 loc[] 和 iloc[]. 场景一: 如果我们要取这个 dataframe 的第一行第一列的元素, 咱们怎么取呢? 咱们分别用 loc[] 和 iloc[]来演示一下:
- df.loc['2000-01-01','A']
- df.iloc[0,0]
大家看出了什么名目了没有???loc[row, column]和 iloc[row_index, column_index] 可以达到同样的效果, 都可以查找到指定的数据, 上面代码返回的数据都是 - 1.148187.
场景二: 如何获取某一行的数据(例如第二行), 咱们可以直接如下所示的两种方法获取
- df.loc['2000-01-01']# returns the first row the the dataframe in the form of series
- df.iloc[0]# returns the first row the the dataframe in the form of series
看看我上面很有逼格的英文注释, 大家应该也能理解, 他们都是返回第一行数据, 但是他们的格式是 series, 而不是 list, 这一点大家需要注意哈. 打印他们后的格式如下所示
- A -1.148187
- B 1.584064
- C -0.589693
- D -1.403843
- Name: 2000-01-01 00:00:00, dtype: float64
既然他是 series, 当然啦, 你也可以调用一个非常方便的 series 的 API
场景三: 如何获取某一列的数据(例如第二列), 国际惯例, 咱还是可以通过下面的三种种不同的方式获取获取
- s = df['B']#return a series corresponding the the column labelled 'B'
- df.iloc[:,1]
- df.loc[:,"B"]
前面咱们已经解释了, 其实 dataframe 的本质可以看成一个 dictionary, 因而上述第一种的方式是相当于直接通过 key 值来获取第二列数据. 上面的第二第三种方式是通过 loc 和 iloc 的方式来获取的. 如果大家有看我之前的介绍 Numpy 的文章, 大家肯定能知道 iloc[]其实和 Numpy 里面的 index 几乎一模一样啦. 对了, 上面代码还是忘记了一种获取一列的常用代码, 就是 dot operation. 其实很简单就是直接用 df.B 这一行代码, 也可以获得和上面代码一样的效果. 上面代码的执行结果如下:
- 2000-01-01 -1.148187
- 2000-01-02 -1.310810
- 2000-01-03 -0.049943
- 2000-01-04 -0.359402
- 2000-01-05 -0.880048
- 2000-01-06 1.106137
- 2000-01-07 1.348941
- 2000-01-08 0.376379
- Freq: D, Name: A, dtype: float64
场景四: slicing, 分割. 意思就是分割 dataframe 的一部分. 例如从第一行到第三行 (不包括) 第二列到第四列(包括). 在这种场景下, 它的参数形式和 Numpy 几乎是一样的, 如下所示
- df.loc['2000-01-01':'2000-01-02','B':'D']
- df.iloc[0:2,1:4]
从上面可以看出, 在 slicing 的时候, loc[]是既包括开始也包括结尾的 index 的 (简单来说就是包头也包尾巴), 而 iloc[] 的索引方式是只包括开始的 index 不包括结尾的 index(简单概括就是包头不包尾, 这其实也是大部分 slicing 的方式). 这一点是他们两种方式的一点细微不同. 上面两行代码的返回值是完全一样的, 如下所示:
- B C D
- 2000-01-01 1.584064 -0.589693 -1.403843
- 2000-01-02 -0.920240 -2.752621 0.913722
返回的也是一个 dataframe.
数据类型(Data type)
我们知道 dataframe 是一张数据表, 既然这张表里面装的都是数据, 那就肯定有不同的数据类型, 例如字符串, int,float,boolean 等等. 在正式进入到数据训练之前, 咱心里必须要清楚的知道这些数据的类型. 这里需要知道的一点是虽然 dataframe 里面的数据的类型可能是千奇百怪的, 但是每一列的数据都只有一种类型. 第一咱们来看看通过什么 API 来获取每一列的数据类型.
- #grab all the columns data type
- all_column_types = wine_reviews.dtypes
她的返回结果是一个 series, 如下所示
- country object
- description object
- designation object
- points int64
- price float64
- province object
- region_1 object
- region_2 object
- taster_name object
- taster_twitter_handle object
- title object
- variety object
- winery object
- dtype: object
第二个应用场景是获取某一列的数据类型(例如咱们想知道 price 的数据类型), 咱们可以通过下面的方式获得它的数据类型
- #grab the type of a column in a dataframe
- column_type = wine_reviews.price.dtype
它的返回结果是
dtype('float64')
还有一个咱们经常要用到的功能是获取 dataframe 的 index 和 column 的名字, 咱们可以通过下面的代码分别获取到 dataframe 的 index 和 colum
- wine_reviews.index
- wine_reviews.columns
她的返回结果是 Index 的对象, 而不是 list 的对象, 这个细节大家需要注意一下.
最后一个咱们经常需要用到的关于数据类型的功能就是类型转化了(convert, 偶尔来个洋文装个逼, 哈哈). 在实际操作中, 咱们需要经常用到将字符串或者 bool 型的数据转化成 INT 或者 float 等, 才能在机器学习中进行计算, 恰恰咱们获取的数据还大部分不是 int 或者 float, 所以类型转化的应用频率还是非常高的, 下面来演示一个将整型 int 类型转化成 float 类型的例子
- #convert a column data type to another type with astype function
- wine_reviews.points.astype('float',copy = False)
astype()函数将原来的 dataframe 中的 price 的 int 类型全部转化成了 float 型. 这里咱们就先演示这个简单的例子, 而不去演示将 string 转化成 int 的例子, 因为那涉及到了特征工程 (feature engineering) 的内容, 咱们在后面需要花大篇幅讲的, 咱们这里先卖个关子. 所以关于数据类型方面的知识, pandas 中主要就是以上的一些方法, 这些方法的最终目的其实都是帮助我们更加深刻的理解咱们的数据, 相当于打一个辅助. 哈哈
calculation functions (翻译过来应该叫做计算函数)
calculation function 听起来还挺高大上的, 其实就是 pandas 的 API 提供的一系列非常方便的操作函数, 例如可以直接获取一个 series 的中位数, 平均数, 最大最小数等等这些常见的计算. 其实为了大家的方便, 我已经把一些经常用到的函数总结在下面了, 每一个函数都有对应的英文注释. 这篇文章如果能看到这里, 我相信你们肯定能知道每一个函数的作用. 这里我就不做细节的解释了.
- #returns the max value in the series of points
- wine_reviews.points.max()
- #returns the minimun value in the series of points
- wine_reviews.points.min()
- #median value of a series(colum
- median_points = wine_reviews.points.median()
- #mean value of a series(column)
- mean_points = wine_reviews.points.mean()
- #the index of maximun value in the column
- index_max = wine_reviews.points.idxmax()
- #the counts of each value in a series, return a series
- value_counts = wine_reviews.country.value_counts()
- #returns an np array, which includes all the value in a series, and excludes duplicates.
- countries = wine_reviews.country.unique()
- #returns the counts of each value in series,exclude duplicates
- countries_number = wine_reviews.country.nunique()
Apply 和 Map
其实 apply 和 map 很像, 很多初学者很容易将他们混淆, 其实他们有一个很明显的不同点, 那就是 apply 通常是 element-wise 的并且运用于整个 dataframe, 而 map 通常也是 element-wise 的并且应用于 series 的. 并且 apply 的参数只能是函数 function, 而 map 的参数既可以是 function 也可以是 dictionary 和 series. 当然啦, series 也可以调用 apply, 但是这通常都是在一些对 series 进行很复杂的运算的的时候才会调用. 记住, 无论是 apply 或者 map 的参数 function, 都可以是匿名函数. 下面先介绍一下 map 的应用.
- def isIndia(country):
- if country == 'India':
- return True
- else:
- return False
- india = wine_reviews.country.map(isIndia)
上面的就是先定义一个函数来判断它的参数是不是等于 "India", 当你用 map 来调用这个函数的时候, 就会把 series 中的每一个 element 都作为参数来传递给 isIndia()函数, 然后用 isIndia()函数返回的每一个值来替代原来的相对应的值. 最后 india 的值如下:
- 0 False
- 1 False
- 2 False
- 3 False
- 4 False
- 129966 False
- 129967 False
- 129968 False
- 129969 False
- 129970 False
- Name: country, Length: 129971, dtype: bool
从上面的返回值可以看出来, 它返回的也是一个 series. 为了实现上面的需求, 咱也有另外一个方式来实现, 那就是直接将匿名函数作为参数传递给 map()函数. 说实话, 匿名函数虽然看起来比较牛逼高大上, 但是实际中我缺不喜欢用, 因为她的可读性和可维护性都不如上面的这种定义函数名的方式. 但是为了能显现咱牛逼, 咱还是掌握一下比较好, 免得到时候看不懂被同组的同事鄙视. 哈哈.... 下面就是用匿名函数的方式实现上面的功能:
US = wine_reviews.country.map(lambda country: True if country == 'US' else False)
先来解释一下匿名函数, 上面 lambda 关键字就是先声明一个匿名函数, 紧接着就是这个匿名函数的参数, 一个冒号: 后面的就是函数体啦.
上面通过一个实例展现了 map 的一些用法, 应该是很简单的, 那么接下来来看看 dataframe 的 apply()函数了. apply()函数其实和 map()是非常相似的, dataframe 调用 apply 的时候, 能将 dataframe 的所用元素都作为参数传递给 apply()里的参数函数, 然后逐一的返回结果. 她的结果还是一个 dataframe. 下面展示一个稍微复杂一点的情况, 就是将一个含有多个参数的函数传递给 apply().
- def substract_custom_value(x,custom_value):
- return x-custom_value
- s.apply(substract_custom_value, args = (5,))
看到虽然 substract_custom_value 函数有两个参数, 当 dataframe 调用 apply 的时候, 默认将 dataframe 的 element 作为第一参数传递给 substract_custom_value 函数, 而 args 的第一个元素作为第二参数传递给 substract_custom_value 函数, 以此类推. 大家千万不要讲参数的数量和顺序弄混了. 上面代码的返回结果是讲 s 里面的每一个元素减去 5.
Grouping
Grouping 也是数据科学中经常用到的一个很重要的功能特性. grouping 是讲 dataframe 按照一定的条件分割成几个小的 "dataframe", 这里为什么会用一个双引号呢, 是因为 grouping 以后得到的并不是一个个 dataframe 类型的数据结构, 其真正的类型是 core.groupby.groupby.DataFrameGroupBy, 因而, 为了咱们理解它, 咱们可以把它看成 "dataframe". 因为它不是真正的 dataframe, 所以很多 dataframe 的 API 它是不能调用的. 这一点是需要重视的. 下面咱们来看看一个简单的案例
group_by_points = wine_reviews.groupby('points')
上面的一行简单的代码, 就讲 wine_reviews 这个 dataframe 分割成很多的 "小 dataframe"--core.groupby.groupby.DataFrameGroupBy, 它内部会将相同 points 的数据整合 (group) 起来作为一个个小整体, 最后会返回很多的这些小整体. 接下来咱们可以对这些小的 "dataframe" 进行很多类似 dataframe 的操作, 例如对他们的 series(实际上是 core.groupby.generic.SeriesGroupBy)进行很多的 calculation function, 就像正常的 dataframe 那样. 例如下面的这个实例, 返回的数据能够很清晰的看出 group 的结构
wine_reviews.groupby('points').country.value_counts()
为了方便看看 DataFrameGroupBy 的結構, 咱们可以直接打印它的结果
- points country
- 80 US 157
- Spain 78
- Argentina 76
- Chile 50
- France 15
- 100 France 8
- Italy 4
- US 4
- Portugal 2
- Australia 1
- Name: country, Length: 463, dtype: int64
我们从上面的结构可以看出 group 将相同 points 的数据整理在一起形成了一个个小的数据块.
同时, 为了更加精细化的控制, 我们经常用到应用多个条件 (conditions) 来 group, 例如, 对于 dataframe wine_reviews, 咱们可以根据 country 和 Provice 两个 conditions 来进行 group, 如下所示
multiple_column_group = wine_reviews.groupby(['country','province'])
上面的代码结果就是, 即使是同一个 country, 不同的 province 也是同属于不同的 group, 它是 multiple index, 而不像上面一个 condition 那样, 只有一个 index(group 中的 index 就是你 group 的那一列, 而不是原来的 index 了). 因而可以实现更加精细化的控制了, 咱们来打印每一个 group 的 points 的 value counts, 其结果如下所示
- multiple_column_group.points.value_counts()
- country province points
- Argentina Mendoza Province 87 400
- 86 353
- 85 349
- 84 346
- 88 319
- Uruguay Uruguay 89 2
- 91 2
- 81 1
- 82 1
- 86 1
- Name: points, Length: 2914, dtype: int64
上面都是分析了 group 后的一些数据的结构, 那么这里有一个问题, 如何将 group 转换回去成为普通的 dataframe 呢? 答案当然是 pandas 都给咱提供了简单易用的 API 啦, 简简单单一句话, 全部搞定, 好了, 直接看下面代码
regular_index_dataframe =multiple_column_group.reset_index(drop = True)
上面一句 easy 的代码, 就都 OK 啦, 所以你有时候不得不佩服 pandas 的强大....
Missing values
在后面的 feature engineering 中我会单独好好讲讲 missing value, 它其实涉及到的知识点还是很多的, 这里就先介绍一下他的基本概念和基础简单的 API, 方便大家理解, 也是为后面真正的特征工程打一个基础吧. 好了, 废话不多说, 咱直接进入主题了. 所谓的 missing value 大家都知道, 实际中搜寻和挖掘数据的过程中, 经常会有一些数据丢失或者说是缺失, 这些数据有可能会影响咱们的最终模型结果. 所以在训练模型之前, 我们有必要先对一些缺失的数据进行处理和修正. 首先咱们得知道某一列中是不是有缺失的数据 null, 咱们可以通过如下的方式获得
is_country_nan = wine_reviews.country.isnull() #returns the masks of the series, which valued true if a nan value
上面的函数返回一个 series, 这个 series 是一个 mask, 即如果这条数据的 country 是空的话, 那么返回 True, 否则返回 False. 如下所示
- 0 False
- 1 False
- 2 False
- 3 False
- 4 False
- 129966 False
- 129967 False
- 129968 False
- 129969 False
- 129970 False
- Name: country, Length: 129971, dtype: bool
上面只是知道哪些数据的 country 是空, 那么如何将他们选出来呢? 放心, pandas 已经为咱们光大的人民群众考虑好啦, 如下所以, 一句代码全搞定
- #to select nan entries by passing a series of boolean value as a parameters
- nan_country_instances = wine_reviews[is_country_nan]
直接将上面返回的 mask 传给 dataframe, 它就返回了所有 country 是空的数据.
那么既然找到了一些 country 为空的数据, 咱们如何 replace 这些空的值呢, 例如将这些空值 NaN 都替换成 "Unknow".pandas 就是为咱们光大屌丝着想哈, 都给咱安排的妥妥的了, 如下所示
- #replace NaN to other values
- fill_nan_country = wine_reviews.country.fillna('Unknow')
总结: 国际惯例, 最后来个大总结哈. 上面从 pandas 的两个基本组成部分 dataframe 和 series 的创建和结构分析开始一直到 pandas 的一个比较高级和常用的 API 应用, 咱们可以基本的了解 pandas 的应用技巧和方法了. 对于从来没有接触过 pandas 和机器学习的小白来说, 想彻底的理解上面的内容和方法, 还是比较困难的 (牛逼的高智商除外). 所以你跟我等凡人一样, 想能熟练的运用 pandas 和深刻的理解它的结构, 必须要用很多的实践和思考, 至少把上面的代码要逐一敲出来并且正确的运行出来, 才能算是初步的入门. 这也是以后要学习特征工程的基础, 如果你把上面的内容都消化了, 足够你应对机器学习方面用到 pandas 知识点, 其实也就那么回事, 在战略上, 咱们要藐视它. 后面要学习的 feature engineering(特征工程) 才是咱们学习机器学习的核心, 所以这一节的基础大家务必夯实.
来源: https://www.cnblogs.com/tangxiaobo199181/p/12147382.html