【項目小結】近期pandas使用細節及技巧盤點

很少寫知識點彙總的博客。主要近期實習基本上都在做這塊事情,在與別人的交流中還是發現自身有相當不足,基礎模塊中還是有很多應當受到重視的,缺少系統的學習鋪墊,很多方法就只能靠經驗積累和文字記錄了。


1 pandas讀取數據表時對類空值字段的預處理

這種情況往往很常見,pandas在讀取外部數據表文件時會對,這源於 pandas.read_table 函數的參數 na_filter , 該參數默認值爲 True 即默認將類似空值的字段(如nan, null, 空字符串等等)在 DataFrame 對象中轉化爲 numpy.nan 的數據類型處理。

以如下數據表文件 sentences.csv 部分截圖爲例,注意該文件爲語料數據庫,不應當存在空值字段👇

import pandas as pd

df = pd.read_table("sentences.csv",header=0,sep="\t",dtype=str)

使用如上代碼讀取它,注意代碼中雖然已經指定了 dtype=str,即以字符串格式讀入所有字段的數據,但仍然會發現截圖中紅線部分222204條與222211條數據被記錄成 NaN (numpy.float 數據類型),這就會導致在後續的字符串處理中出現問題,如果缺少經驗可能很難發現是在讀取文件時出現的錯誤,只需修正參數 na_filter=False 即可避免不必要的問題。

import pandas as pd

df = pd.read_table("sentences.csv",header=0,sep="\t",dtype=str,na_filter=False)

讀入數據就不會將類似空值的字段作爲 NaN 處理。


2 pandas日期格式數據處理細節及技巧

事實上pandas在讀入外部數據表文件時並不會對類似日期格式的字段做轉爲 Datetime 格式的處理,但其實這並不影響日期格式的字段在實際使用中的問題。

以如下數據表文件 inventory.csv 部分截圖爲例👇

 

import pandas as pd

df = pd.read_table("inventory.csv",sep=",",header=0)

print(type(df.loc[0,"date"]))

# 輸出
# <class 'str'>
# ------------------


# 做date字段的篩選

df1 = df[(df.date>="2019-02-01")&(df.date<="2019-02-28")]

print(df1.shape)

# 輸出
# (15423,9)

使用上面的代碼讀入數據表(不指定數據格式),我們先查看date字段的數據格式,發現是字符串。但我們仍然使用date字段去做數據篩選(篩選出2019-02-01~2019-02-28的所有數據記錄),發現仍然可以正常篩選。

字符串之間當然不能比較大小,但是我們仍然可以在不轉化的情況下做類日期字符串之間的比較。

當然一般情況下以防萬一可以對類日期格式的字段做 to_datetime 處理,如下所示👇

import pandas as pd

df = pd.read_table("inventory.csv",sep=",",header=0)

df.date = pd.to_datetime(df.date,format="%Y-%m-%d")

print(type(df.loc[0,"date"]))

# 輸出
# <class 'pandas._libs.tslib.Timestamp'>
# ------------------


# 做date字段的篩選

df1 = df[(df.date>="2019-02-01")&(df.date<="2019-02-28")]

print(df1.shape)

# 輸出
# (15423,9)
# ------------------

# pandas._libs.tslib.Timestamp格式字段的常用屬性

df_week = df.date.dt.weekofyear # 得到該日期是當年的第幾周
df_year = df.date.dt.year # 得到該日期的年號
df_weekday = df.date.dt.dayofweek # 得到該日期是星期幾

# ------------------

經過這種處理後我們仍然可以使用相同的篩選方法(即與字符串作比較),並且在轉化爲日期格式後可以發現有很多常用的方法,如迅速調取日期的週數,年號,星期幾等數據,避免了再使用datetime包的繁瑣步驟。


3 關於 map 函數與 apply 函數的用法

不知道其他人是不是也有過這種奇怪的經歷,以前一直把 apply 函數當作 map 函數在用,然後第一次見到 map 函數的例子後發現竟然是與apply函數同樣的用法,心生困頓然後纔去弄明白了apply函數的正確用法。

3.1 map 函數用法

import pandas as pd

df = pd.DataFrame({
	"A": [1,2,3,4],
	"B": [2,3,4,5],
	"C": [3,4,5,6],
})

df.A = df.A.map(lambda x: x**2) # 將A列全部平方

print(df)

# 輸出

'''
    A  B  C
0   1  2  3
1   4  3  4
2   9  4  5
3  16  5  6

'''

自定義一個 DataFrame 後我們將它的A列平方處理,非常簡明的用法,參數中當然可以寫一個更加複雜的函數,這裏是一個簡單的平方函數。

近期我發現 map 函數還可以用在字段名稱的映射上,比如現在我們有需求要將所有字段名稱中的大寫字母全部變成小寫字母👇

import pandas as pd

df = pd.DataFrame({
	"A": [1,2,3,4],
	"B": [2,3,4,5],
	"C": [3,4,5,6],
})

df.columns = df.columns.map(lambda x: x.lower())

print(df)

# 輸出

'''

   a  b  c
0  1  2  3
1  2  3  4
2  3  4  5
3  4  5  6

'''

這樣看起來 DataFrame 格式的數據應該是在存儲時將 df.columnsdf.index 都作爲與 數據列 / 行 類同的數據格式處理了。

3.1 apply 函數用法

map函數不同之處在於,apply函數可以作用到多個 數據列 / 行 上,當然前面代碼中的 map 函數的代碼都可以被 apply 函數不作修改的直接取代,關鍵在於apply在對多個 數據列 / 行 的處理上的用法。

import pandas as pd

df = pd.DataFrame({
	"A": [1,2,3,4],
	"B": [2,3,4,5],
	"C": [3,4,5,6],
})

# 對同行數據做處理 axis=1

def f1(df):
	return df[0]+df[1]+df[2]

df["total"] = df[["A","B","C"]].apply(f1,axis=1)

print(df)

# 輸出

'''
   A  B  C  total
0  1  2  3      6
1  2  3  4      9
2  3  4  5     12
3  4  5  6     15
'''

# -----------------------------

# 對同列數據做處理 axis=0

def f2(df):
	return df[0]+df[1]+df[2]+df[3]

total = df.apply(f2,axis=0)

print(total)

# 輸出

'''
A        10
B        14
C        18
total    42
dtype: int64
'''

# -----------------------------

使用上面的代碼,一般我們遇到的情況都是通過對某些字段列數據做運算後生成一列新數據,如需要對行求和,則需要指定axis=1,因爲該參數默認爲0,不指定則會發現 total 列全部爲 NaN。如果需要對列數據求和,則指定axis=0即可。


4 雜記

  1. 在讀取大文件數據讀取時如果本身就已經做好了要分塊處理的準備,可以在 pandas.read_table 函數中設置 chunk_size 參數,該參數默認爲None,即不做分塊處理,則默認將全部數據讀入內存。若設置該參數爲一個整型數(如16),則會將數據以類生成器的格式分爲16塊存儲到一個類列表中(這邊我的描述比較抽象,可以親自嘗試一下就會有所體會),這樣做一方面讀取數據極快(不論你設置 chunk_size 爲多少都很快,一般來說越大越快),另一方面因爲讀入內存的是生成器,幾乎不會佔用內存,且生成器格式的數據很方便後續將數據載入模塊寫成生成器函數來使用(如深度學習中使用批訓練方式)。另外最近python又出了一個新的數據處理包Vaex(抄襲我嵩哥[滑稽]),聲稱0.052秒即可讀入100G的數據,其邏輯是避免將數據全部讀入內存,而只是在運算時對少量數據進行抽取,再將結果返回。
  2. groupby函數確是很好用,而且感覺裏面有很多方法,熟練掌握可以大大地減少代碼量。當然如果記憶力不好就用下面一段代碼也差不多夠用了👇
    import pandas as pd
    
    data = pd.read_table("inventory.csv",header=0,sep=",")
    
    groups = data.groupby(["sku_id","plant_id"])
    
    for name,df in groups:
        print(name)
        print(df)
        # 對df做處理唄就
        # ......

    反正一個個做處理也不會很慢。

  3. 常用的表的連接:內連接與左連接的區別(數據庫的東西經常就會遺忘掉)

    df = df1.merge(df2,on="group_id",how="left")  df2中找不到df1的匹配值則置空
    df = df1.merge(df2,on="group_id",how="inner")  df2中找不到df1的匹配值則刪除

    (待續)


可能很多人都已經知道或者掌握筆者提到的問題,但筆者認爲pandas庫本身還是需要長期使用積累熟練度,身邊一些朋友長時間不做數據處理回過頭就會發現手生,又得花點時間從頭過一遍。本文筆者可能會偶爾更新,一些簡單的技巧就不多作贅述,別人都已經寫過很多了。筆者主要就近期發現的問題做盤點與記錄。

分享學習,共同進步!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章