整理歸納在Python中使用對數據處理的常用方法,包括與HDFS文件的讀寫,主要是怕用的時候記不住,容易搞混,再搜也不爽,好記性不如爛筆頭,寫下來自己用的時候方便看,而且寫一遍也加深印象。
隨查隨用隨更新
Numpy
ndarray
ndarr1 = np.array([ [1, 2, 3], [4, 5, 6] ])
shape 大小和形狀
dtype 數據類型
astype 顯式的轉換類型
不同大小的ndarray也可以運算,稱之為廣播,比如arr * 2
一維:用[x]進行索引,用[x:y]進行切片,都是原始數組的視圖
多維:[x][y]或者[x,y]索引,[x1:x2, y1:y2]切片。
布爾型可以直接做索引
按軸進行布爾類型list的索引,或者對每個元素進行單個布爾索引,比如data1[data1 > 0] = 0
花式索引行和列
arr[ [行1, 行i] ][ :, [列1, 列i] ] 或者
arr[ np.ix_( [行1, 行i], [列1, 列i] ) ]
而且花式索引總是復制數據到新的數組中,不是切片那種原始數組的視圖
arr.T 轉置
通用函數
np.sqrt(arr) arr的開平方
np.exp(arr) arr的e的x次方
np.abs(arr) arr的絕對值
np.square(arr) arr的平方
np.log(arr) ln(arr)
np.log10(arr) log10(arr)
np.isnan arr的各個元素是否為NaN的bool數組
np.add(arr1, arr2) arr1 + arr2
np.subtract(arr1, arr2) arr1 - arr2
三元表達式
np.where(cond, xarr, yarr) 等價為 xarr if cond else yarr
統計
arr.sum(axis=?) 按照軸的方向求和
arr.mean 按照軸的方向求平均值
arr.min()和max() 最大和最小值
arr.argmin()和argmax() 最大和最小值的索引
arr.cumsum() 所有元素的累計和
arr.cumprod() 所有元素的累計積
排序
np.sort(arr)和arr.sort(),前者是頂級方法,返回對arr排序后的副本;后者則是直接對arr排序。
線性代數
包括矩陣的點乘,矩陣分解以及行列式等等。
點乘:xarr.dot(yarr)等價于np.dot(xarr, yarr)
numpy.linalg中有一組標準的矩陣分解運算以及諸如求逆和行列式之類。比如如下部分:
diag([一維數組]) 對角矩陣,對角線是輸入的一維數組
dot 點乘
det 計算矩陣的行列式
inv 計算矩陣的逆
qr 計算QR分解
svd 計算奇異值分解(SVD)
Pandas
(官網)https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html
官網Pandas的API匯總
官網10分鐘對Pandas的簡介
Series
可以把Series看成是一個有序的定長字典。
ser = Series([1, 2, 3, 4], index=['d', 'b', 'a', 'c'])
經常存在與DataFrame的行與列中。
也可以直接從字典類型的對象中構建。
idx_val_map = {'d':1, 'b':2, 'a':3, 'c':4}
ser = Series(idx_val_map, name='值的名稱')
ser.index.name = '索引的名稱'
ser['a']進行索引
再進一步,比如對于二維的DataFrame,取其中一列出來,就是Series,它的name就是這一列的名字,它的index就是DataFrame的index,index的name就是DataFrame中作為index的那一列的名字。
DataFrame
DataFrame的索引是不可更改的。可以添加和刪除,但是不能修改。
- 構造DataFrame
1.由二維ndarray構造,可以給出行標和列標
2.由字典構成,外層字典key是列標,內層字典是行標result1 = pd.DataFrame([[1, 0.7], [0, 0.7]], index=pd.Index([135, 136], name='Number'), columns=['Gender', 'Prob'])
需要注意的是字典構成的df,需要指定column的順序,否則默認會按照列的字符串名排序
predict_df = pd.DataFrame(data={'svc_y': svc_y, 'dnn_y': dnn_y, 'xgb_y': xgb_y, 'rf_y': rf_y}, columns=['svc_y', 'dnn_y', 'xgb_y' ,'rf_y'])
3.由另一個DataFrame構成
有時候會有一種需求是從已經有的DataFrame中創建一個空的Dataframe但是使用的是原來這個Dataframe的結構。
copy_df = pd.DataFrame.from_items([(name, pd.Series(data=None, dtype=series.dtype))
for name, series in original_df.iteritems()])
-
添加列
frame['new-col'] = value # 需要添加多列時比較特殊點需要借Dataframe來賦值 frame[['new-col1', ..., 'new-coln']] = pd.Dataframe(np.random.randint(1,5,[n_rows, n]), index=frame.index)
-
添加行
s = pd.Series({'Col1':'Value1', 'Col2': 'Value2', 'Col3': Value3}) # 構造一行新數據 frame.append(s)
-
刪除列
del frame['col-name']
-
刪除行
# 因為DataFrame是列索引的(在frame.items()返回值中可以看出來)所以del只能刪除列,刪除行需要用到drop方法 frame.drop(frame.index[[1,3]])
-
索引方式
列索引:frame['col_name']或者frame.col_name
行索引:frame.ix['row_name']
點索引:frame.at[行,列] 或者frame.iat[行i,列i],只能索引具體的元素,不能切片
用整數位置索引frame.iloc[index(列表), col-index(列表)]
ix[行, 列]和iloc[行, 列]都可同時索引行與列,區別在于ix用的是名字(當DataFrame的index沒有設置時,退化成iloc),iloc用的是整數位置
loc: works on labels in the index.
iloc: works on the positions in the index (so it only takes integers).
ix: usually tries to behave like loc but falls back to behaving like iloc if the label is not in the index.- 總結
['col-name']——是對列的索引
[ ['col-name1', 'col-name_i'] ]——兩個[]是對多個列的索引
[bool類型list]或者[i:j]——是對行的切片索引
ix[行,列]——支持對行和列同時索引和切片(loc和iloc參考上面)NOTE:ix操作如果有左值,那么就默認會copy,如果沒有左值那么就是對原始Frame進行操作,而loc和iloc默認都是對原始Frame的操作,所以推薦使用loc,如果需要復制,可以顯式的調用Frame.copy(). 不過仍要注意的是frame2 = frame1.loc[a:d, :]確實不是復制是對原frame1的切片,但是frame1.loc[ [a, b, c, d], :]則是復制不是切片了。
at[行,列]和iat[i,j]——是定位到元素
所以對列的bool類型list切片應該用ix[:,bool_list],然而對行的bool類型list切片可以直接[bool_list]
- 總結
-
基本功能
重新索引
reindex,對索引重新排序,新加的使用缺失值填充。丟棄指定軸上的項
frame.drop(['name'], axis=?)切片
frame[:2] 這個是直接按行切片,和常識有點不太一樣,但是方便實際操作。
frame[ ['列1', '列i'] ],這個是按列切片過濾
按照某一列的值過濾:frame[ frame['列'] > 5 ]
對于bool類型的list,除了上面的方法,也可以frame.loc[bool_list_X, bool_list_Y]進行過濾。算術運算
frame1 + frame2 或者 frame1.add(frame2, fill_value=0)
sub做減法 div是除法 mul是乘法-
函數應用和映射
Numpy的ufuncs也可用于操作pandas對象np.abs(frame)
還可以自定義函數(列或行的):
f = lambda x: x.max() - x.min()
frame.apply(f, axis=?)對元素的函數
format = lambda x: '%.2f' % x
frame.applymap(format)featQuant = sampleDF_feat.quantile(0.999) # 獲得0.999的分位數
frameFeatUnderQuant = sampleDF_feat.where(sampleDF_feat <= featQuant, other=0) # 把異常高的數值排除掉總的來說,DataFrame可以用的是apply和applymap,Series可以用的是map:
apply:是對DataFrame的具體行或者列進行整體操作。比如可以取最大最小值
applymap:是對DataFrame的具體每個元素進行操作。
map:也是對每個元素進行操作,和applymap類似。 排序
frame.sort_index(by='name', axis=?, ascending=True)
-
匯總和計算描述
frame.sum(axis=?, skipna=True) 求和
frame.mean() 平均值
frame.count()
frame.min、max
frame.argmin、argmax 整數索引位置
frame.idmin、idmax 名字ID索引位置
frame.quantile() 計算樣本的分位數(0到1),比如frame['col_name'].quantile([q/10 for q in range(1, 11, 1)])
frame.describe() # 描述各個列的詳細統計信息
frame.info() # 描述各個列是否有缺失值和dtype
frame['col_name'].value_counts() # 計算得到col_name列上各種取值的個數- groupby
這個操作是對數據做聚合后的操作。
比如df['col1'].groupby(by=某種依據)
,就是在df的'col'列上按照某種依據來聚合,產生一個DataFrameGroupBy對象。這里的某種依據就是告訴pandas在聚合時哪幾個數據屬于一類的,比如df的行數為7,那么某種依據為['c','s','t','t','s','s','c'],會把第一行和最后一行歸在一起,第2\5\6行歸在一起,第3\4行歸在一起。
此時可以使用mean(),sum(),apply()這樣的操作來得到想要的值。當對df['col1']做groupby時得到的每一項是Series,此時apply(lambda x: ...)中的x是Series類型;當對df[['col1',...,'colN']]做groupby時得到的是Dataframe,此時apply(lambda x: ...)中的x是DataFrame類型。
比如對df['cnt']表示某個用戶購買某個物品的個數,我們想得到每個用戶各自購買物品占自己購買總數的比值時:df['cnt'].groupby(df['user_name']).apply(lambda cnts: cnts.apply(lambda n: n / cnts.sum()))
另外df.groupby(...).agg(func)
是指對df做group之后對每一列執行func,如果不需要對每列執行,則直接使用apply即可。agg的詳細示例見:官網agg - melt
這個是讓df按照指定的列消融,比如df有3列,直接melt(),會得到新的melt_df由index, variable, value這三個列組成,其中index的范圍是原來的3倍,variable是原來的列名,value是原來對應列的值。
melt()函數有幾個參數:id_vars='不消融的列', value_vars=消融的列list, var_name='消融后variable的名字', value_name='消融后value的名字'
- groupby
-
處理缺失數據
frame.dropna() 丟棄NaN,可以給出閾值進行調節容忍度
df.fillna( {'列1': 值1, '列i': 值i}, inplace=True ) 默認返回一個新的對象
frame.isnull() 返回與frame一樣大小的布爾值對象(值為NaN的為True,否則為False) -
其他常用
# 去重 df.duplicated(keep='first') # 這個會返回列的值重復與否的布爾值,默認對于重復值的第一個為False,其他重復的為True # 得到這個布爾后可以再進行切片去重,不過還有更便捷的方法: df['某列'].drop_duplicates(keep='first', inplace=True) # 這樣直接得到去重后的結果。
-
從文件中讀取
frame = pd.read_csv('file.csv', names=col_names, index_col='col_index')
另外說下,如果csv的文件中是這樣的0,1,2,"string:\"(1,2)\""
,那么上面在讀的過程中會有問題,多讀出一列,需要加上參數escapechar="\\"
,也就是:
frame = pd.read_csv('file.csv', names=col_names, index_col='col_index', escapechar="\\")
除了讀取本地文件還可以讀取HDFS文件:首先需要安裝需要的包:pip install hdfs 官網文檔
然后代碼中:from hdfs import Client from hdfs import HdfsError client = Client('http://name_node.url:50070') # 輸入要鏈接的NameNode地址。如果是高可用的模式,會有多個name node,那么有個比較笨的方法就是挨個試,看看那個可用(也就是處于active狀態)就用哪個。 print(client.list('/')) # 可以在這里添加try操作,catch到異常后去嘗試下一個name node的鏈接。 try: with client.read('path/file.csv') as hdfs_in_fs: predictDF = pd.read_csv(hdfs_in_fs, names=predict_cols, index_col='Number') except HdfsError as e: print(e)
-
將Frame寫入文件
frame.to_csv(path_or_buf='path/file', index=True, header=False, sep='|')
寫入HDFS文件會有些麻煩,一是寫的時候需要權限,把要寫入的目錄設置為hdfs dfs -chmod -R 777 write_Dir
;二是寫入文件會遇到TypeError: a bytes-like object is required, not 'str',因為不像普通的文件可以在open的時候設置mode參數,所以還需要做如下操作:from hdfs import Client from hdfs import HdfsError client = Client('http://name_node.url:50070') # 輸入要鏈接的NameNode地址。如果是高可用的模式,會有多個name node,那么有個比較笨的方法就是挨個試,看看那個可用(也就是處于active狀態)就用哪個。 print(client.list('/')) # 可以在這里添加try操作,catch到異常后去嘗試下一個name node的鏈接。 try: with client.write('path/file' overwrite=True, encoding='utf-8') as hdfs_out_fs: # 需要設置encoding不然遇到上面說的TypeError predictDF[['Gender', 'Prob']].to_csv(path_or_buf=hdfs_out_fs, index=True, header=False, sep='|') except HdfsError as e: print(e)
-
另外一種讀寫HDFS文件的方法
上面的代碼確實可以讀寫HDFS文件,不過需要修改權限,這會出現下面情況:可以看到為了允許讀寫,上級目錄已經將權限更改為全部可讀寫執行了,而且新創建的文件夾和文件都是用戶dr.who
這會給后續操作帶來一定的麻煩,比如yarn用戶在程序中對這個文件無法修改。因為只有讀權限。
不過還有一個API可以在python中使用,如果你的HDFS配置支持非安全的HttpFS的話。Client API介紹上面的使用Client的API是用的WebHDFS。連接的時候需要指定namenode,而在高可用的模式情況下,可能要多試幾次才知道當前哪個node處于active狀態。為了避免這兩個問題,接下來展示另外一個API InsecureClient來實現。在接下來的代碼中,使用的是HttpFS方式來讀寫HDFS
from hdfs import InsecureClient client = InsecureClient('http://HttpFS_node.url:14000', user='yarn') # 使用HttpFS所配置的角色主機域名及REST 端口 print(client.list('/')) # 無需像之前一樣多次嘗試當前輸入的namenode是否可用 with client.read('path/in_file') as hdfs_in_fs: with client.write('path/out_file', overwrite=True, encoding='utf-8') as hdfs_out_fs: sampleDF = pd.read_csv(hdfs_in_fs, names=, index_col=) sampleDF.to_csv(path_or_buf=hdfs_out_fs, index=True, header=False, sep='|')
這個API的好處是:1、不需要修改權限,由參數的指定用戶保證權限沒問題。2、新寫入的文件權限也無問題。3、可以支持遠程讀寫,無需在集群上執行,在本地PC上運行即可。4、無需知道當前active的namenode是哪個,可以通過HttpFS主機直接連接。
新建的文件都是用戶yarn的 -
合并數據集
- pandas.merge
根據一個或多個鍵將不同的DataFrame中的行連接起來。類似SQL的關系型數據庫的連接操作
簡單說就是將多個DataFrame按照指定的各自的列以某種方式(inner,left,right,outer)組合起來。
除了pd.merge還有frame_left.join(frame_right)這個方式
其中join和merge的區別如下:
left.join(right, on='col_name') #是將left的col_name列和right的index進行比較連接,且默認how='left' left.merge(right, on='col_name') #是將left和right共有的col_name列進行比較連接,且默認how='inner' # 不過如果right中如果沒有col_name列,那么還是會和join一樣會拿index來連接. # 另外merge還提供了left_on/right_on/left_index/right_index這些參數供選擇
- pandas.concat
沿著一條軸將多個對象堆疊在一起
比如將兩個frame的行數據堆疊起來。
frame_total = pd.concat([frame1, framei], axis=?, keys=['col1_name', 'coli_name']) - 舉栗
合并Series到Dataframe中:purchase_1 = pd.Series({'Name': 'Chris', 'Item Purchased': 'Dog Food', 'Cost': 22.50}) purchase_2 = pd.Series({'Name': 'Kevyn', 'Item Purchased': 'Kitty Litter', 'Cost': 2.50}) purchase_3 = pd.Series({'Name': 'Vinod', 'Item Purchased': 'Bird Seed', 'Cost': 5.00}) df = pd.DataFrame([purchase_1, purchase_2, purchase_3], index=['Store 1', 'Store 1', 'Store 2']) s = pd.Series({'Name':'Kevyn', 'Item Purchased': 'Kitty Food', 'Cost': 3.00}) s.name = 'Store 2' # 合并s到df中 # 方法1,使用concat函數 df = pd.concat([df, s.to_frame().T]) # 方法2,使用append df = df.append(s)
- pandas.merge
-
打印DataFrame
打印DataFrame非常簡單直接print就可以了,但是Pandas對于顯示的行數、列數、寬度和長度都有默認限制,如果不期望看到...這樣的省略號,或者不期望換行,那么可以如下設置:
在最近的pandas版本中,已經沒有display.height這個參數了,其值可由display.max_rows自動推斷得到。pd.set_option('display.height',1000) # 把1000換成None即為無限制,下面均是如此。 pd.set_option('display.max_rows',500) pd.set_option('display.max_columns',500) pd.set_option('display.width',1000)
上面的設置完之后會對后面的代碼全部做這個配置處理,如果想在代碼中動態的改變,還可以這么寫:
with pd.option_context('display.max_rows', None, 'display.max_columns', None, 'display.width', None): logging.info('輸出打印DataFrame:\n{}'.format(data_frame))
這樣在需要長顯示的時候套上with就可以了,不需要的地方繼續使用pandas的默認設置。
pandas的通用功能(官鏈)
pandas提供了很多general function來支持很多數據操作。比如上面提到過的pandas.melt
還有其他經常用到的pandas.concat, pandas.merge, pandas.unique
等,下面會介紹一些其他常用的功能:
pd.cut
我們有時會需要把連續數值轉換為類別,比如根據取值的大小按照某種分隔標準來劃分成類別。
更明確些,我們可能有兩種劃分依據,在pandas中均能找到對應的函數:1、按照取值來劃分,比如某個特征取值從0到1,我們希望按照這個取值范圍分成3等份,那么就可以用pd.cut;2、按照統計的個數來劃分,比如根據樣本在這個特征上的取值所統計的個數,分為好中差三個等樣本個數的分類,這時可以用pd.qcut;
下面簡單介紹下幾個常用參數
pd.cut(x=連續取值的數據, bins=整數N(會自動按照上下限的取值份N等份)/數組(會按照數組指示的間隔來劃分), labels=劃分后的標記)
比如X的取值是0到1,希望按照(0, 0.25](0.25, 0.5](0.5, 0.75](0.75, 1]劃分成4份,且對應的label分別是1到4:
pd.cut(X, bins=[i*1.0/4 for i in range(0, 4+1)], labels=range(1,4+1))
-
pd.qcut
介紹幾個常用的參數
pd.cut(x=連續取值的數據, q=整數N(會自動按照統計分位數分N等份)/數組(會按照數組指示的間隔來劃分), labels=劃分后的標記)
比如q = [0, .25, .5, .75, 1.] for quartiles
pd.qcut(range(5), 3, labels=["good", "medium", "bad"])
... [good, good, medium, bad, bad] Categories (3, object): [good < medium < bad]
pd.get_dummies
這個是對類別特征做one-hot編碼,比如pd.get_dummies(df, columns=df.columns)
但是有個點需要注意下,比如現在有訓練集和測試集,其中訓練集中可能某個類別沒有在測試集中出現,或者測試集中的某個類別沒有在訓練集中出現。此時如果分別對它們做get_dummies就會出現得到的數據集的列對不起來。
此時需要做的是,保證訓練集中的列一定都會出現,對于測試集中沒有包含訓練集的列添加并值為0,對于測試集中含有訓練集中沒有的去除,并保證兩個數據集的列順序一致。
df_train_onehot = pd.get_dummies(train_df, columns=train_df.columns)
train_oh_cols = df_train_onehot.columns
df_test_onehot = pd.get_dummies(test_df, columns=test_df.columns)
test_oh_cols = df_test_onehot.columns
miss_cols = set(train_oh_cols) - set(test_oh_cols)
for col in miss_cols:
df_test_onehot[col] = 0
df_test_onehot = df_test_onehot[train_oh_cols]
除了上面這個做法外,還可以利用sklearn的OneHotEncoder來實現。
ohe = OneHotEncoder(sparse=False, handle_unknown='ignore')
然后在ohe.fit_transform(train_df)
接著ohe.transform(test_df)
即可,不過ohe返回的值都是np.ndarray類型,如果需要則自己命名列的名字。
第一種方法要保存的是訓練集的列名,第二種方法則要把fit后的ohe模型保留下來。
Matplotlib
常用作圖方法
import matplotlib.pyplot as plt
%matplotlib inline # 在ipynb上顯示圖片
plt.plot([4, 3, 2, 1]) # 作圖代碼
plt.show() # 在python運行程序時顯示
# 啟動ipython作圖時可以: ipython --pylab,這樣就會自動import matplotlib.pyplot as plt而且不用show()就可以輸出圖表。
plt.plot([4, 3, 2, 1]) # 使用ipython --pylab后直接顯示圖圖表
直接用數據作圖
- 對于numpy的數組數據作圖:
fig = plt.figure()
ax1 = fig.add_subplot(2, 2, 1)
ax2 = fig.add_subplot(2, 2, 2)
ax3 = fig.add_subplot(2, 2, 3)
plt.plot([1.5, 3.5, -2, 1.6]) # 默認對最后創建的subplot作圖
import numpy as np
from numpy.random import randn
_ = ax1.hist(randn(100), bins=20, color='k', alpha=0.3) # 直方圖
ax2.scatter(np.arange(30), np.arange(30) + 3 * randn(30)) # 散列圖
- 強調數據點
plt.plot(randn(30).cumsum(), 'ko--') # 等價于下面語句
plt.plot(randn(30).cumsum(), color='k', linestyle='dashed', marker='o')
- 圖中坐標和標題設置
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(randn(1000).cumsum())
ticks = ax.set_xticks([0, 250, 500, 750, 1000]) # 設置X軸的刻度
labels = ax.set_xticklabels(['one', 'two', 'three', 'four', 'five'], rotation=30, fontsize='small') # 刻度的標簽
ax.set_title('My first matplotlib plot') # 圖的標題
ax.set_xlabel('Stages') # X軸的名稱
- 添加圖例
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(randn(100).cumsum(), 'k', label='one') # 創建圖例
ax.plot(randn(100).cumsum(), 'g--', label='two')
ax.plot(randn(100).cumsum(), 'r.', label='three')
ax.legend(loc='best') # 把上面設置的圖例(legend)創建生效
- 保存圖表到文件
plt.savefig('figpath.svg')
plt.savefig('figpath.png', dpi=400, bbox_inches='tight')
Pandas的繪圖函數
- Series做柱狀圖
import pandas as pd
from pandas import Series, DataFrame
fig, axes = plt.subplots(nrows=2, ncols=1) # 獲得subplot集合
data = Series(np.random.rand(16), index=list('abcdefghijklmnop'))
data.plot(kind='bar', ax=axes[0], color='k', alpha=0.7) # 豎向柱狀圖,不設置kind默認是線形圖
data.plot(kind='barh', ax=axes[1], color='k', alpha=0.7) # 橫向柱狀圖
- DataFrame做柱狀圖
df = DataFrame(np.random.rand(6, 4),
index=['one', 'two', 'three', 'four', 'five', 'six'],
columns=pd.Index(['A', 'B', 'C', 'D'], name='Genus'))
print(df)
df.plot(kind='bar')
df數據打印:
Genus A B C D
one 0.017426 0.964258 0.479931 0.636357
two 0.020693 0.979753 0.846889 0.436802
three 0.650068 0.608675 0.964375 0.866141
four 0.523848 0.610598 0.296204 0.879183
five 0.419329 0.023081 0.442044 0.842727
six 0.926948 0.454734 0.436056 0.970364
- 直方圖和密度圖
comp1 = np.random.normal(0, 1, size=200) # N(0, 1)
comp2 = np.random.normal(10, 2, size=200) # N(10, 4)
values = Series(np.concatenate([comp1, comp2])) # 合并為一個Series
values.hist(bins=100, alpha=0.3, color = 'k', normed=True) # 直方圖
values.plot(kind='kde', style='k--') # 密度圖(kde表示標準混合正態分布)
- 散布圖
plt.scatter(trans_data['m1'], trans_data['unemp']) # 散布圖
plt.title('Changes in log %s vs. log %s' % ('m1', 'unemp'))
pd.plotting.scatter_matrix(trans_data, diagonal='kde', color='k', alpha=0.3) # 散布圖矩陣