上文交代了一些啰嗦事,本文開始,就要來點實際的了。
先來一個比較簡單的場景:
Given:一個包括N(極其復雜,這里取3個)個列的DataFrame:df,df包括index;
And:對df所有列元素進行一些處理,得到df的一個變換后的df_new;
And:對df_new的某些列做極其復雜的判斷,得到新的列result;
When:需要將要根據result對df進行分析;
Then:將result追加到df中。
上述的場景是我的一個算法驗證的場景,簡單地說,就是需要通過對原始數據進行變換,然后得到新的列,然后追加到原始數據中,最后進行分析。
首先構造一下模擬的數據吧。我們的數據通過隨機數產生,為10*3的數據,每一列的名稱為A/B/C,放到csv格式的文件中,文件內容如下所示:
A,B,C 4.556325895482557,4.9467487190814206,9.240498646959768 6.798122785026925,9.205498509979439,6.495701755638054 1.7088666262153485,1.9490481646738644,5.016719349132167 7.68793618140002,4.288529993589748,0.4479283787649413 0.22238136867848257,2.861119654701667,5.296500633944277 3.8352546975711,0.29287579880826087,1.0438719791356377 4.603712955967749,5.647815101448938,0.6054047619225811 6.916870514198006,8.903690009637602,4.836793019361064 9.845396552800361,0.8159084013183737,0.8425827491512894 2.425545747141858,7.353661205806686,2.982326067390466
一般字符型的文件會以上述的逗號隔開的方式存儲,通過pandas的read_csv()可以方便的讀入數據到DataFrame,如下代碼所示:
import pandas as pd import numpy as np df = pd.read_csv('d:/data.csv')
pd.read_csv()默認的讀法就是上述代碼那樣,他會將第一行當做列名讀入。假如我們的csv數據中不包含列名,可以通過header=None來禁止將第一行當做列名;假如csv數據中沒有列名,而我們想要指定列名,這也是我經常做的,可以通過name=[]參數,通過一個列表指定所有列的名稱。指定列名有一個好處就是列名可以當做DataFrame的屬性,對列進行訪問,后續會介紹。
讀進來的輸入如下所示:
然后,我們需要對df進行變換。
這里模擬一種變換,就是將每一列進行均值位0,方差為1的變換,代碼如下:
import pandas as pd import numpy as np df = pd.read_csv('d:/data.csv') df_new = df.apply(lambda col: (col - col.mean()) * 1.0 / col.std()) df_new
其中,df.apply()函數是df的一個函數式接口,表示依次對df的列(A/B/C)進行迭代,其參數為一個lambda表達式,lambda的參數col就表示df的一個列。
col其實是Series的一個對象,其中包含大量的數據處理方法,這里實用的是其中兩個:均值mean()和std()方差。
lambda的實現部分使用了Pandas的向量化的運算方式,也就是說col雖然是一個Series,但是我們對其操作就像操作一個標量一樣,但是自動傳播給所有的元素。
經過上述代碼的運算,每一列都被映射到新的df_new中,這個方式在機器學習、數據挖掘中很常見。結果如下圖所示。
A | B | C | |
---|---|---|---|
0 | -0.102247 | 0.100417 | 1.865099 |
1 | 0.652462 | 1.435738 | 0.944303 |
2 | -1.060854 | -0.839506 | 0.448149 |
3 | 0.952021 | -0.105966 | -1.084541 |
4 | -1.561285 | -0.553528 | 0.542008 |
5 | -0.344998 | -1.358795 | -0.884620 |
6 | -0.086294 | 0.320234 | -1.031713 |
7 | 0.692439 | 1.341107 | 0.387790 |
8 | 1.678338 | -1.194799 | -0.952147 |
9 | -0.819582 | 0.855099 | -0.234328 |
現在,我們要使用df_new的數據計算我們的結果了。如果邏輯很簡單,則可以直接通過Pandas的IF THEN邏輯實現,復雜的算法可以需要自己一行一行的遍歷df_new,通過if else邏輯實現。我們先說一個簡單的情況。
假如,我們的算法是這樣的:如果A>0.5並且B<0.5,則為True,否則如果C>0.5,則為True,則否為False,實現代碼如下所示:
import pandas as pd import numpy as np df = pd.read_csv('d:/data.csv') df_new = df.apply(lambda col: (col - col.mean()) * 1.0 / col.std()) df_new.ix[(df_new.A > 0.5) & (df_new.B < 0.5), 'Result'] = True df_new.ix[~((df_new.A > 0.5) & (df_new.B < 0.5)) & (df_new.C > 0.5), 'Result'] = True df_new.ix[~(~((df_new.A > 0.5) & (df_new.B < 0.5)) & (df_new.C > 0.5)), 'Result'] = False df_new
結果如下所示:
由於算法邏輯不是很復雜,抽象起來也就三個情況,所以通過Pandas的IF Then的機制實現了Result列的添加。
DataFrame.ix[condition, 'newOrExistsColname'] = value,具體可以參考Pandas的十分鍾入門教程,開始就是講這個的。
需要強調的是我們通過df_new.A這樣的方式訪問A列元素,這是Pandas的方便之處;並且各種比較和& ~操作都是針對元素的;並且只有index相同的元素才會比較,而且Result也是添加到對應的index列的。
假如算法實現在太復雜,不是簡單的集中情況可以講清楚的,這個時候我采用的方法就是C++和Java中最常使用的for循環了,幾乎萬能。還以上述的算法為例,代碼如下:
import pandas as pd import numpy as np df = pd.read_csv('d:/data.csv') df_new = df.apply(lambda col: (col - col.mean()) * 1.0 / col.std()) result = pd.Series([False]*len(df_new)) for i in range(len(df_new)): item = df_new.iloc[i, :] if item.A > 0.5 and item.B < 0.5: result[i] = True elif item.C > 0.5: result[i] = True else: result[i] = False # unnecessary df_new['Result'] = result df_new
代碼中一片濃郁的C、Java風格,雖然不那么函數式也不向量化,但是我就是感覺熟悉,畢竟我最早學習的是C。
首先,我定義了一個Series,初始化為False,然后通過一個for循環,遍歷df_new的所有行,依次為result賦值,最后,通過df_new['newcolname']=的方式將resut追加到df_new中了。
注意,當將Series往DataFrame或者Series中拼接的時候,默認的都是安裝index對齊的,也就是行的index一致的才會拼接到一起,而與行的順序無關,這點是Pandas的強大之處(數據的自動對齊,在groupby之后的數據中特別有用),千萬別忽視了這點。
最后,我們要將df_new.Result追加給df,因為df_new.Result本質上還是一個Series,所以上述的代碼中已經有了一種方式:df.ix['Result']=df_new.Result。
代碼如下:
import pandas as pd import numpy as np df = pd.read_csv('d:/data.csv') df_new = df.apply(lambda col: (col - col.mean()) * 1.0 / col.std()) result = pd.Series([False]*len(df_new)) for i in range(len(df_new)): item = df_new.iloc[i, :] if item.A > 0.5 and item.B < 0.5: result[i] = True elif item.C > 0.5: result[i] = True else: result[i] = False # unnecessary df_new['Result'] = result df['Result'] = df_new.Result df
最終結果如下:
本文要點:
Pandas讀取CSV文件方法;
Pandas的追加新列方法:df.ix[condition, 'newcolname'] = value(標量或者向量) 或者 df['newcolname'] = value(標量或者向量);
Pandas的df.apply()對列進行迭代的方法;
Pandas的df中的一些常用的數據處理方法;
Pandas的Series的簡單邏輯操作& | ~;
看,是不是很簡單。
未完待續……