1. 程式人生 > >機器學習-Pandas 知識點彙總(吐血整理)

機器學習-Pandas 知識點彙總(吐血整理)

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(特徵工程)才是咱們學習機器學習的核心,所以這一節的基礎大家務必夯實。