Panas 101 Numpy 101 and RegularExpression
Why?
筆者會寫這篇文章,主要是因為,過去在實作各項資料科學專案,包含視覺化,或是機器學習預測類型的專案,往往腦袋想一套,實際上操作時卻卡到不行,最近終於有閒暇時間,好好打底,實作了Pandas101, Numpy 101及RegularExpression之後,最近操作資料的效率變高了,也能夠更快速的驗證自己對資料的想法,如果讀者和筆者有一樣的痛(跪),那你就來對地方了XD
這篇會講些什麼?
- 當初有這樣做就好了
- 進行Data Science Work Flow時常用的15種pandas處理
- 轉換為實際行動
當初有這樣做就好了
起初的筆者,憑著一股莫名其妙的熱情,Kaggle上kernel抓了就開始啃,了解背後的ML model為什麼這樣做,急著建立自己的ML專案,然而,卻沒有非常注意到自己pandas的function, numpy的function,查了又查,錯了又錯,無形之中浪費了不少時間。
如果當初能夠重視到處理資料的打底,和ML的專業知識並行,想必一定能夠有更好的效率,自主實作時也能夠少更多噴錯,以更有學習成就感的方式往前邁進。
所以筆者建議
找Pandas 101, Numpy 101, RegularExpression來刷,每天各刷個3~5題,重點在於,不管找了什麼資源,不論是上述3樣,Kaggle 的教學kernel,書,一定要自己實作,這樣才能有最好的學習效果,美國學者艾德格、戴爾(Edgar Dale)已經提出了學習金字塔的概念來和我們說明怎麼樣能夠有更好的效果:
進行Data Science Work Flow時常用的15種pandas處理
1. 使用dir查找可用屬性及方法:
這招基本上是python內建的,不過作者發現在針對groupby物件,datetime64物件,或是series.strings.StringMethods物件中快速地查看非常方便,省了大量查文件的時間,畢竟pandas的文件長到那個地步,有時候index要找也不知道要下什麼字的那種情況……dir拯救世界。
# for string
ser = pd.Series(['how', 'to', 'kick', 'ass?'])
print(dir(ser.str))# for groupby
df = pd.DataFrame({'col1': ['apple', 'banana', 'orange'] * 3,
'col2': np.random.rand(9),
'col3': np.random.randint(0, 15, 9)df_grouped = df.groupby(['col1'])
print(dir(df_grouped))# for datetime64
ser = pd.Series(['01 Jan 2010', '02-02-2011', '20120303', '2013/04/04', '2014-05-05', '2015-06-06T12:20'])
tmp = pd.to_datetime(ser)
dir(tmp.dt)
2. df.assign()
寫了一陣子的 df[‘new_feature’] = …..之後,發現寫df.assign真的更簡潔,也更好讀。
# 創造lag特徵及laed特徵
df = pd.DataFrame(np.random.randint(1, 100, 20).reshape(-1, 4), columns = list('abcd'))df = df.assign(aLag1 = df.a.shift(1),
bLead1 = df.b.shift(-1))
3. df.query()
比起使用df[condition],開始使用df.query()吧,這樣有更好的可讀性,在資料量大時也有更好的效能。
df = pd.read_csv('https://raw.githubusercontent.com/selva86/datasets/master/Cars93_miss.csv')highest_price = df['Price'].max()
df.query(f'Price == {highest_price}')
4. Groupby
Groupby的使用頻率高得不像話,不過你知道除了dataframe自己groupby,一條series還能夠以另一條series當作key來groupby嗎? 嘗試看看這個新的想法吧!
fruit = pd.Series(np.random.choice(['apple', 'banana', 'carrot'], 10))
weights = pd.Series(np.linspace(1, 10, 10))tmp = pd.DataFrame({'fruit':fruit,
'weights':weights}).groupby('fruit').mean()tmp['weights'].index.name = ''
不過目標series經過groupby之後,series的name要重新調整。
5. df.groupby.agg() in version 0.25
groupby.agg也是非常常用的function,針對一組key做groupby之後,我們想要看多種匯總值,最大的困擾就是很難轉回tidy-form, 0.25版本中出現了這樣的寫法,搭配as_index=False以及reset_index()之後可能回到tidy-form,這簡直是一大福音!
titanic = pd.read_csv('http://bit.ly/kaggletrain')
bad_idea = titanic.groupby('Pclass').agg({'Age':['mean','max'],
'Survived':['mean']})def tidy_groupby_df(df):
tidy_df = titanic.groupby('Pclass').agg(ave_age=('Age','mean'),
max_age=('Age','max'),
survival_rate=('Survived','mean'))return tidy_df.reset_index()tidy_df = tidy_groupby_df(titanic)
display(bad_idea,
tidy_df)
6. where系列
在pandas中針對dataframe或是series做條件篩選,最直覺的作法是apply + lambda function,並在function中寫if,是時候使用where了,可讀性更好,而且是向量化的,資料量大時有更好的性能,在資料清理及特徵工程中都非常常用。
pd.Series.where
pd.DataFrame.where
np.where
np.argwhere
其中最常用的是pd.Series.where及np.where,若要取得index,才會使用np.argwhere,
若要多組條件,只要在包兩層np.where即可!
# 保留出現頻率最高的兩個values, 其他換成"Other"np.random.RandomState(100)
ser = pd.Series(np.random.randint(1, 5, [12]))top_2_frequent = ser.value_counts().nlargest(2).index
ser.where(ser.isin(top_2_frequent), other='Other')
7. pd.cut與pd.qcut
面對連續行數值欄位,經常需要做binning,為了更好的解釋性或是減輕overfitting,也算是在EDA及特徵工程時相當常用的方法。
cut是針對數值做切分,qcut則是針對分位數做切分,值得一提的是,兩種方法的output type能夠是Categorical,Categorical的型態是能夠比大小的 :
ser = pd.Series(np.random.random(20))pd.qcut(ser,
q = [0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1],
labels=['1st', '2nd','3rd','4th','5th',
'6th', '7th', '8th','9th', '10th'])
8. pd.concat
在進行特徵工程時,時常會創造新的特徵,需要與原本的dataframe做合併,或是寫在function之中。
# vertically, horizontally
ser1 = pd.Series(range(5))
ser2 = pd.Series(list('abcde'))df1 = pd.concat([ser1,ser2], axis='index').to_frame()
df2 = pd.concat([ser1,ser2], axis='columns')
display(df1.head(), df2)
9. pd.Series.str + RegularExpression
文字的判斷及抽取真的是從結構化資料入門的DS的痛,不要猶豫了快學一下正則表達式吧!筆者花在RegilarExpression的練習大約才4小時,之後就能夠比較有概念的操作文字!
# 過濾出至少有兩個母音的valueser = pd.Series(['Apple', 'Orange', 'Plan', 'Python', 'Money'])condition = ser.str.count('[aeiouAEIOU]') >= 2
ser[condition]# output [(zuck26),(facebook),(com),...]emails = """zuck26@facebook.com
page33@google.com
jeff42@amazon.com"""pat = re.compile(r'(\w+)@(\w+).([A-Za-z]{2,4})')
re.findall(pat, emails)
10. TimeSeries Index
面對TImeSeries的處理,常常是很難在文件中找到,要記得這個pd.date_range函式啊
dateime_idx = pd.date_range('2000-01-01', periods=10, freq='W-SAT')pd.Series(index = dateime_idx,
data = np.random.randint(2,8,size=len(dateime_idx)))
11. df.melt
常常從excel拿到資料時,發現想要畫圖的值怎麼都在columns上,melt就是把它塞回column用的!
data = {'weekday': ["Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", "Sunday"],
'Person 1': [12, 6, 5, 8, 11, 6, 4],
'Person 2': [10, 6, 11, 5, 8, 9, 12],
'Person 3': [8, 5, 7, 3, 7, 11, 15]}
df = pd.DataFrame(data, columns=['weekday',
'Person 1', 'Person 2', 'Person 3'])df_result = df.melt(id_vars=['weekday'],
value_vars=['Person 1','Person 2','Person 3'],
var_name='PersonNo',value_name='Score'
)
12. glob + pd.read_csv
一次讀取多個csv檔,不管是進行concat,或是分別預測,初次遇到還真不知道怎麼做比較順……
以下示範一個資料夾之下的10個csv檔,一次讀取進來,並且配在dictionary當中
# create fake csv
FOLDER_DIR = './csvset'
for i in range(11):
path = FOLDER_DIR + '/' + f'data_{i}.csv'
pd.DataFrame(np.random.randint(low=5, high=100, size=(10,10))).\
to_csv(path, index=False)# read it
from glob import glob
DATA_PATH_LIST = glob('./csvset/*.csv')
PREFIX = 'data'
dfSet = {}
for idx, path in enumerate(DATA_PATH_LIST):
df_name = PREFIX + f'_{idx}'
dfSet[df_name] = pd.read_csv(path)
display(df_name,
dfSet[df_name].head(2))
同樣的道理,我們也可以把他們concat成一張大的dataframe,記得在concat時,要指定ignore_index=True,否則index不對,就會產生null value。
# create fake csv
FOLDER_DIR = './csvset'
for i in range(11):
path = FOLDER_DIR + '/' + f'data_{i}.csv'
pd.DataFrame(np.random.randint(low=5, high=100, size=(10,10))).\
to_csv(path, index=False)
# concat it
from glob import glob
DATA_PATH_LIST = glob('./csvset/*.csv')
dfList = [pd.read_csv(file) for file in DATA_PATH_LIST]# concat
pd.concat(dfList, ignore_index=True)
13. pd.get_dummies()
相信大家都遇過需要one-hot-encoding的時候,但是如果使用notebook,真的很容易無法重現結果的情況,一個好的解法是用不同的名稱,例如concat_result。
此外,get_dummies也可以產生sparse-form的matrix
df = pd.DataFrame(np.arange(25).reshape(5,-1), columns=list('abcde'))concat_list = [df.drop(columns=['a']),
pd.get_dummies(df['a'])]result = pd.concat(concat_list, axis=1)
result
14. df.explode()
0.25的新功能!當你的原始資料columns裡面有一是不規則數量的list時,用explode大大節省時間,不用回到dictionary慢慢洗。
df = pd.DataFrame({'sandwich':['PB&J','BLT','cheese'],
'ingredients':[['peanut butter','jelly'],
['bacon','lettuce','tomato'],
['swiss cheese']]},
index=['a','b','c'])display(df)
df.explode('ingredients')
15. scipy.spatial.distance.pdist
關於距離函數,自己一個一個寫實在太沒效率了,打開scipy的pdist吧,裡面近乎是常用的eudidean, cosine, hamming, …..都有支援了,對於特徵工程及EDA來說非常實用。
# 計算euciden距離
df = pd.DataFrame(np.random.randint(1,100, 40).reshape(10, -1), columns=list('pqrs'), index=list('abcdefghij'))
display(df.head())def euclidean_dist(df):
from scipy.spatial.distance import pdist, squareform
row_name = df.index.tolist()
dist = pdist(df, 'euclidean')
df_dist = pd.DataFrame(squareform(dist), columns=row_name, index=row_name)
return df_dist# 計算euciden距離,並產生兩個新欄位,最近的value以及對應的距離def get_neast_and_euclidean_dist(df):
from scipy.spatial.distance import pdist, squareform
row_name = df.index.tolist()
dist = pdist(df, 'euclidean')
df_dist = pd.DataFrame(squareform(dist), columns=row_name, index=row_name)
nearest_list = []
nearest_dist_list = []
# instead of list comprehension
# for loop is more readable for complex operation
for row in df_dist.index:
nearest_info = df_dist.loc[row, :].sort_values()
nearest_idx, nearest_dist = nearest_info.index[1], nearest_info.values[1]
nearest_list.append(nearest_idx)
nearest_dist_list.append(nearest_dist)
df_dist['nearset'] = nearest_list
df_dist['dist'] = nearest_dist_list
return df_dist
get_neast_and_euclidean_dist(df)
轉換為實際行動
以上就是筆者分享的15個常用的pandas技巧,由於自己過去花了太多時間在處理錯誤上,如果讀者也是因為同樣的原因看到這篇文章,希望能夠給你們一點指引,現在就開始刷pandas 101, numpy 101, 以及RegularExpression吧!
此外,筆者自己也針對pandas101, numpy101進行整理,並思考更具可讀性,向量化的寫法,並註解上在Data Science Workflow中會使用到的場景,有興趣的讀者也可以在我的GitHub頁面找到,若有什麼問題,也歡迎來信yltsai0609@gmail.com討論! Happy Analysis!