天下java本站尽知

超级管理员

35605

帖子

9

回复

54

积分

楼主
发表于 2020-01-04 22:00:02 | 查看: 296 | 回复: 0

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(特征工程)才是咱们学习机器学习的核心,所以这一节的基础大家务必夯实。

您需要登录后才可以回帖 登录 | 立即注册