基於 Python 和 NumPy 開發的 Pandas,在數據分析領域,應用非常廣泛。而使用 Pandas 處理數據的第一步往往就是讀入數據,比如讀寫 CSV 文件,而Pandas也提供了強勁的讀取支持,參數有 38 個之多。這些參數中,有的容易被忽略,但卻在實際工作中用處很大。比如:
文件讀取時設置某些列為時間類型
導入文件,含有重復列
過濾某些列
每次迭代讀取 10 行
而pandas讀取csv文件時通過read_csv函數讀取的,下面我們就來好好看看這個函數生得一副什么模樣,都有哪些參數。
read_csv中的參數
以下都是read_csv中的參數,但是根據功能我們划分為不同的類別。
基本參數
filepath_or_buffer
數據輸入路徑,可以是文件路徑,也可以是 URL,或者實現 read 方法的任意對象。就是我們輸入的第一個參數。
In [2]: pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data') Out[2]: 5.1 3.5 1.4 0.2 Iris-setosa 0 4.9 3.0 1.4 0.2 Iris-setosa 1 4.7 3.2 1.3 0.2 Iris-setosa 2 4.6 3.1 1.5 0.2 Iris-setosa 3 5.0 3.6 1.4 0.2 Iris-setosa 4 5.4 3.9 1.7 0.4 Iris-setosa .. ... ... ... ... ... 144 6.7 3.0 5.2 2.3 Iris-virginica 145 6.3 2.5 5.0 1.9 Iris-virginica 146 6.5 3.0 5.2 2.0 Iris-virginica 147 6.2 3.4 5.4 2.3 Iris-virginica 148 5.9 3.0 5.1 1.8 Iris-virginica [149 rows x 5 columns]
sep
數據文件的分隔符,默認為逗號。
舉例:下面的girl.csv 文件的分割符為 \t
,如果使用 sep 默認的逗號分隔符,讀入后的數據混為一體。
In [4]: df = pd.read_csv('girl.csv') In [5]: df Out[5]: name\tage\tgender 0 椎名真白\t18\t女 1 古明地覺\t17\t女 2 古明地戀\t16\t女 # 分隔符指定錯誤,所以多個列的數據連在一塊了
sep 必須設置為 '\t'
,數據分割才會正常。
In [1]: df = pd.read_csv('girl.csv', sep='\t') In [2]: df Out[2]: name age gender 0 椎名真白 18 女 1 古明地覺 17 女 2 古明地戀 16 女
delimiter
分隔符的另一個名字,與 sep 功能相似。
delimiter_whitespace
0.18 版本后新加參數,默認為 False,設置為 True 時,表示分割符為空白字符,可以是空格、 \t
等。
如下 girl.csv 文件分隔符為\t
,設置 delim_whitespace 為 True:
In [4]: df = pd.read_csv('girl.csv',delim_whitespace=True) In [5]: df Out[5]: name age gender 0 椎名真白 18 女 1 古明地覺 17 女 2 古明地戀 16 女 # 不管分隔符是什么,只要是空白字符,那么可以通過delim_whitespace=True進行讀取
header
設置導入 DataFrame 的列名稱,默認為 'infer'
,注意它與下面介紹的 names 參數的微妙關系。
names
當names沒被賦值時,header會變成0,即選取數據文件的第一行作為列名。
當 names 被賦值,header 沒被賦值時,那么header會變成None。如果都賦值,就會實現兩個參數的組合功能。
我們舉例說明
-
names 沒有被賦值,header 也沒賦值:
In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True) In [2]: df Out[2]: name age gender 0 椎名真白 18 女 1 古明地覺 17 女 2 古明地戀 16 女 # 我們說這種情況下,header為變成0,即選取文件的第一行作為表頭
-
names 沒有賦值,header 被賦值
In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True, header=1) In [2]: df Out[2]: 椎名真白 18 女 0 古明地覺 17 女 1 古明地戀 16 女 # 不指定names,指定header為1,則選取第二行當做表頭,第二行下面的是數據
-
names 被賦值,header 沒有被賦值
In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True, names=["姓名", "年齡", "性別"]) In [2]: df Out[2]: 姓名 年齡 性別 0 name age gender 1 椎名真白 18 女 2 古明地覺 17 女 3 古明地戀 16 女 # 我們看到names適用於沒有表頭的情況 # 指定names沒有指定header,那么header相當於None # 一般來說,讀取文件會有一個表頭的,一般是第一行,但是有的文件只是數據而沒有表頭 # 那么這個時候我們就可以通過names手動指定、或者生成表頭,而文件里面的數據則全部是內容 # 所以這里那么name、age、gender也當成是一條記錄了,本來它是表頭的,但是我們指定了names,所以它就變成數據了,表頭是我們在names里面指定的
-
names和header都被賦值
In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True, names=["姓名", "年齡", "性別"], header=0) In [2]: df Out[2]: 姓名 年齡 性別 0 椎名真白 18 女 1 古明地覺 17 女 2 古明地戀 16 女 # 這個相當於先不看names,只看header,我們說header等於0代表什么呢?顯然是把第一行當做表頭,下面的當成數據 # 好了,然后再把表頭用names給替換掉 # 再來看個栗子 In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True, names=["姓名", "年齡", "性別"], header=1) In [2]: df Out[2]: 姓名 年齡 性別 0 古明地覺 17 女 1 古明地戀 16 女 # header=1,表示第二行當做表頭,第二行下面當成數據 # 然后再把表頭用names給替換掉,就是上面的結果 # 所以一般情況下,有表頭並且是第一行,那么names和header都無需指定 # 但是有表頭、而表頭不是第一行,可能從下面幾行開始才是真正的表頭和數據,這個是指定header即可 # 如果沒有表頭,全部是純數據,那么我們可以通過names手動生成表頭 # 而names和header都指定的情況下,一般是有表頭但是這個表頭你不想用,所以用names來把原來的表頭替換掉,其實就等價於讀取之后再對列名進行rename
index_col
我們在讀取文件之后,生成的索引默認是0 1 2 3...,我們當然可以set_index,但是也可以在讀取的時候就指定某個列為索引
In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True, index_col="name") In [2]: df Out[2]: age gender name 椎名真白 18 女 古明地覺 17 女 古明地戀 16 女 # 這里指定"name"作為索引,另外除了指定單個列,還可以指定多個列,比如["name", "age"] # 並且我們可以輸入列的名字,也可以輸入對應的索引。比如:name, age, gender,它們對應的索引就是0, 1, 2
usecols
如果列有很多,而我們不想要全部的列、而是只要指定的列就可以使用這個參數。
In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True, usecols=["name", "age"]) In [2]: df Out[2]: name age 0 椎名真白 18 1 古明地覺 17 2 古明地戀 16 # 這里只要name和age兩列
mangle_dupe_cols
實際生產用的數據會很復雜,有時導入的數據會含有重名的列。參數 mangle_dupe_cols 默認為 True,重名的列導入后面多一個 .1
。如果設置為 False,會拋出不支持的異常:
# ValueError: Setting mangle_dupe_cols=False is not supported yet
prefix
prefix 參數,當導入的數據沒有 header 時,設置此參數會自動加一個前綴。比如:
In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True, header=None) In [2]: df Out[2]: 0 1 2 0 name age gender 1 椎名真白 18 女 2 古明地覺 17 女 3 古明地戀 16 女 # 我們看到在不指定names的時候,header默認為0,表示以第一行為表頭 # 但如果不指定names、還顯式地將header指定為None,那么會自動生成表頭0 1 2 3... # 因為DataFrame肯定是要有列名(表頭)的 In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True, header=None, prefix="xx") In [2]: df Out[2]: xx0 xx1 xx2 0 name age gender 1 椎名真白 18 女 2 古明地覺 17 女 3 古明地戀 16 女 # 而prefix就是給這樣的列名增加前綴的 # 但感覺不是很常用
通用解析參數
dtype
筆者就曾遇到一件比較尷尬的事情,就是處理地鐵人員數據的。工作人員的id都是以0開頭的,比如0100012521,這是一個字符串。但是在讀取的時候解析成整型了,結果把開頭的0給丟了。這個時候我們就可以通過dtype來指定某個列的類型,就是告訴pandas你在解析的時候按照我指定的類型進行解析。
In [1]: df = pd.read_csv('girl.csv', delim_whitespace=True, dtype={"age": str}) In [2]: df Out[2]: name age gender 0 椎名真白 18 女 1 古明地覺 17 女 2 古明地戀 16 女 # 這里就表示要把age解析成字符串
engine
pandas解析數據時用的引擎,Pandas 目前的解析引擎提供兩種:C、Python,默認為 C,因為 C 引擎解析速度更快,但是特性沒有 Python 引擎全。如果使用 C 引擎沒有的特性時,會自動退化為 Python 引擎。
比如使用分隔符進行解析,如果指定分隔符不是單個字符、或者"\s+"
,那么C引擎就無法解析了。我們知道如果分隔符為空白字符的話,那么可以指定delim_whitespace=True
,但是也可以指定sep=r"\s+"。
In [1]: df = pd.read_csv('girl.csv', sep=r"\s+") In [2]: df Out[2]: name age gender 0 椎名真白 18 女 1 古明地覺 17 女 2 古明地戀 16 女 # 如果sep是單個字符,或者字符串\s+,那么C是可以解決的。 # 但如果我們指定的sep比較復雜,這時候引擎就會退化。 # 我們指定的\s{0}相當於沒指定,\s+\s{0}在結果上等同於\s+。 # 但是它不是單個字符,也不是\s+,因此此時的C引擎就無法解決了,而是會退化為python引擎 In [1]: df = pd.read_csv('girl.csv', sep=r"\s+\s{0}", encoding="utf-8") In [2]: df Out[2]: ParserWarning: Falling back to the 'python' engine because the 'c' engine does not support regex separators (separators > 1 char and different from '\s+' are interpreted as regex); you can avoid this warning by specifying engine='python'. name age gender 0 椎名真白 18 女 1 古明地覺 17 女 2 古明地戀 16 女 # 我們看到雖然自動退化,但是彈出了警告,這個時候需要手動的指定engine="python"來避免警告 # 這里面還用到了encoding參數,這個后面會說,因為引擎一旦退化,在Windows上不指定會讀出亂碼 # 這里我們看到sep是可以支持正則的,但是說實話sep這個參數都會設置成單個字符 # 基本上在生成文件的時候,分隔符不會復雜到讀取的時候需要使用正則來分隔的。
converters
可以在讀取的時候對列數據進行變換
In [1]: df = pd.read_csv('girl.csv', sep="\t", converters={"age": lambda x: int(x) + 10}) In [2]: df Out[2]: name age gender 0 椎名真白 28 女 1 古明地覺 27 女 2 古明地戀 26 女
看到少女們都長了10歲,完成對 age 列的數據加 10,注意 int(x),此處解析器默認所有列的類型為 str,所以需要顯式類型轉換。
true_values和false_value
指定哪些值應該被清洗為True,哪些值被清洗為False。這兩位老鐵需要成對出現,只出現一個沒有效果。但是說實話,這個不常用
In [1]: df = pd.read_csv('girl.csv', sep="\t") In [2]: df Out[2]: name age gender 對錯 0 椎名真白 18 女 對 1 古明地覺 17 女 錯 2 古明地戀 16 女 對 # 加了一個字段 In [1]: df = pd.read_csv('girl.csv', sep="\t", true_values=["對"], false_values=["錯"]) In [2]: df Out[2]: name age gender 對錯 0 椎名真白 18 女 True 1 古明地覺 17 女 False 2 古明地戀 16 女 True
skiprows
skiprows 過濾行,想過濾掉哪些行,就寫在一個列表里面傳遞給skiprows即可。注意的是:這里是先過濾,然后再確定表頭,比如:
In [1]: df = pd.read_csv('girl.csv', sep="\t", skiprows=[0]) In [2]: df Out[2]: 椎名真白 18 女 對 0 古明地覺 17 女 錯 1 古明地戀 16 女 對 # 我們把第一行過濾掉了,但是第一行是表頭 # 所以過濾掉之后,第二行就變成表頭了 # 這里過濾第二行 In [1]: df = pd.read_csv('girl.csv', sep="\t", skiprows=[1]) In [2]: df Out[2]: name age gender 對錯 0 古明地覺 17 女 錯 1 古明地戀 16 女 對
里面除了傳入具體的數值,來表明要過濾掉哪一行,還可以傳入一個函數
In [1]: df = pd.read_csv('girl.csv', sep="\t", skiprows=lambda x: x > 0 and x % 2 == 0) In [2]: df Out[2]: name age gender 對錯 0 椎名真白 18 女 對 1 古明地戀 16 女 對 # 由於索引從0開始,凡是索引大於0、並且%2等於0的記錄都過濾掉 # 索引大於0,是為了保證表頭不被過濾掉
skipfooter
從文件末尾過濾行,解析引擎退化為 Python。這是因為 C 解析引擎沒有這個特性。
In [1]: df = pd.read_csv('girl.csv', sep="\t", skipfooter=1, encoding="utf-8", engine="python") In [2]: df Out[2]: name age gender 對錯 0 椎名真白 18 女 對 1 古明地覺 17 女 錯 # skipfooter接收整型,表示從結尾往上過濾掉指定數量的行 # 因為引擎退化為python,那么要手動指定engine="python",不然會警告 # 另外需要指定encoding="utf-8",因為csv存在編碼問題,當引擎退化為python的時候,在Windows上讀取會亂碼
nrows
nrows 參數設置一次性讀入的文件行數,它在讀入大文件時很有用,比如 16G 內存的PC無法容納幾百 G 的大文件。
low_memory
這個看起來是和內存有關的,但其實它是和數據類型相關的。在解釋這個原因之前,我們還要先從DataFrame的數據類型說起。
我們知道得到DataFrame的每一列都是有類型的,那么在讀取csv的時候,pandas也是要根據數據來判斷每一列的類型的。但pandas主要是靠"猜"的方法,因為在讀取csv的時候是分塊讀取的,每讀取一塊的時候,會根據數據來判斷每一列是什么類型;然后再讀取下一塊,會再對類型進行一個判斷,得到每一列的類型,如果得到的結果和上一個塊得到結果不一樣,那么就會發出警告,提示有以下的列存在多種數據類型:
DtypeWarning: Columns (1,5,8,......) have mixed types. Specify dtype option on import or set low_memory=False.
而為了保證正常讀取,那么會把類型像大的方向兼容,比如第一個塊的user_id解釋成整型,但是第二個塊發現user_id有的值無法解析成整型的,那么類型整體就會變成字符串,於是pandas提示該列存在混合類型。而一旦設置low_memory=False,那么pandas在讀取csv的時候就不分塊讀了,而是直接將文件全部讀取到內存里面,這樣只需要對整體進行一次判斷,就能得到每一列的類型。但是這種方式也有缺陷,一旦csv過大,就會內存溢出。
但是從數據庫讀取就不用擔心了,因為數據庫是規定了每一列的類型的。如果是從數據庫讀取得到的DataFrame,那么每一列的數據類型和數據庫表中的類型是一致的。
還有,我們在上面介紹了dtype,這個是我們手動規定類型。那么pandas就會按照我們規定的類型去解析指定的列,但是一旦無法解析就會報錯。
memory_map
如果你知道python的一個模塊mmap,那么你肯定很好理解。如果使用的數據在內存里,那么直接進行映射即可,不會再次進行IO操作。默認為False
空值處理相關參數
na_values
na_values 參數可以配置哪些值需要處理成 NaN,這個是非常常用的,但是用的人不多。
df = pd.read_csv('girl.csv') print(df) """ name age gender 0 mashiro 18 女 1 # 17 女 2 koishi 16 # """ # 將#全部換成NaN df = pd.read_csv('girl.csv', na_values=["#"]) print(df) """ name age gender 0 mashiro 18 女 1 NaN 17 女 2 koishi 16 NaN """ # 也可以指定多個值 df = pd.read_csv('girl.csv', na_values=["#", "mashiro"]) print(df) """ name age gender 0 NaN 18 女 1 NaN 17 女 2 koishi 16 NaN """ # 也可以只對指定的列進行替換 df = pd.read_csv('girl.csv', na_values={"name": ["#"], "gender": ["女"]}) print(df) """ name age gender 0 mashiro 18 NaN 1 NaN 17 NaN 2 koishi 16 # """
keep_default_na 是和 na_values 搭配的,如果前者為 True,則 na_values 被解析為 Na/NaN 的字符除了用戶設置外,還包括默認值。默認為True
skip_blank_lines
skip_blank_lines 默認為 True,則過濾掉空行,如為 False 則解析為 NaN
verbose
打印一些重要信息
時間處理相關參數
parse_dates
指定某些列為時間類型。
df = pd.read_csv("xx.csv", parse_dates=["column"])
date_parser
date_parser 參數定制某種時間類型,詳細使用過程總結如下。因為有些格式雖然是日期,但不是那種可以直接轉換的樣子:比如'2018-01-01'。可能是這種類型:'2018年1月1日',這個時候我們就需要手動來定制解析的規則
df = pd.read_csv("xx.csv", parse_dates=["column"], date_parser=lambda x: pd.datetime.strptime(x, "%Y年%m月%d日"))
infer_datetime_format
infer_datetime_format 參數默認為 False。如果設定為 True 並且 parse_dates 可用,那么 Pandas 將嘗試轉換為日期類型,如果可以轉換,轉換方法並解析,在某些情況下會快 5~10 倍。
分塊讀入相關參數
分塊讀入內存,尤其單機處理大文件時會很有用。
iterator
iterator 取值 boolean,默認為False。如果為True,那么返回一個 TextFileReader 對象,以便逐塊處理文件。這個在文件很大時,內存無法容納所有數據文件,此時分批讀入,依次處理。
# 此時返回一個類似於迭代器的對象 chunk = pd.read_csv('girl.csv', iterator=True) # 調用get_chunk,里面傳入整型,來讀取指定的行數 print(chunk.get_chunk(1)) """ name age gender 0 mashiro 18 女 """ print(chunk.get_chunk(2)) """ name age gender 1 # 17 女 2 koishi 16 # """ try: print(chunk.get_chunk(2)) except StopIteration: print("文件讀取完畢") # 文件讀取完畢 # 文件總共三行,所以讀完之后,再讀就溢出了 # 如果還剩下1行,但是我們指定讀取100,那么也不會報錯 # 不夠指定的行數,那么有多少返回多少
chunksize
chunksize 整型,默認為 None,設置文件塊的大小。
chunk = pd.read_csv('girl.csv', chunksize=2) # 還是返回一個類似於迭代器的對象 print(chunk) # <pandas.io.parsers.TextFileReader object at 0x00000258C41B2CD0> # 調用get_chunk,如果不指定行數,那么就是默認的chunksize print(chunk.get_chunk()) """ name age gender 0 mashiro 18 女 1 # 17 女 """ # 但也可以指定 print(chunk.get_chunk(1)) """ name age gender 2 koishi 16 # """ try: print(chunk.get_chunk(2)) except StopIteration: print("文件讀取完畢") # 文件讀取完畢
格式和壓縮相關參數
compression
compression 參數取值為 {‘infer’, ‘gzip’, ‘bz2’, ‘zip’, ‘xz’, None}
,默認 ‘infer’
,直接使用磁盤上的壓縮文件。
如果使用 infer 參數,則使用 gzip、bz2、zip 或者解壓文件名中以 ‘.gz’、‘.bz2’、‘.zip’ 或 ‘xz’ 這些為后綴的文件,否則不解壓。
如果使用 zip,那么 ZIP 包中必須只包含一個文件。設置為 None 則不解壓。
# 直接將上面的girl.csv添加到壓縮文件,打包成girl.zip df = pd.read_csv('girl.zip', compression="zip") print(df) """ name age gender 0 mashiro 18 女 1 # 17 女 2 koishi 16 # """ # 會自動解包,讀取文件,但是如果沒有compression參數,那么就會報錯了 # UnicodeDecodeError: 'utf-8' codec can't decode byte 0xce in position 0: invalid continuation byte
thousands
千分位分割符,如 ,
或者 .
,默認為None
encoding
encoding 指定字符集類型,通常指定為 'utf-8'。根據情況也可能是ISO-8859-1
error_bad_lines和warn_bad_lines
如果一行包含過多的列,假設csv的數據有3列,但是某一行卻有4個數據,顯然數據有問題。那么默認情況下不會返回DataFrame,而是會報錯。
# pandas.errors.ParserError: Error tokenizing data. C error: Expected 3 fields in line 3, saw 4
我們在某一行中多加了一個數據,結果顯示錯誤。因為girl.csv里面有三列,但是有一行卻有四個數據,所以報錯。
在小樣本讀取時,這個錯誤很快就能發現。但是如果樣本比較大、並且由於數據集不可能那么干凈、很容易出現這種情況,那么該怎么辦呢?而且這種情況下,Excel基本上是打不開這么大的文件的。這個時候我們就可以將error_bad_lines設置為False(默認為True)
,意思是遇到這種情況,直接把這一行給我扔掉。同時會設置 warn_bad_lines 設置為True,打印剔除的這行。
df = pd.read_csv('girl.csv', error_bad_lines=False, warn_bad_lines=True) print(df) """ name age gender 0 mashiro 18 女 1 koishi 16 # b'Skipping line 3: expected 3 fields, saw 4\n' """
以上兩參數只能在C解析引擎下使用。
總結
pandas在讀取csv的時候支持的參數是很多的,其中部分參數也適用於讀取其它類型的文件。這些參數雖然很多平常都不會用,但是還是要了解,因為read_csv的其它參數是可以很方便地解決某些問題的。