- 原文地址:Overview of Pandas Data Types
- 原文作者:Chris Moffitt
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:stormluke
- 校對者:Starrier、luochen1992
簡介
在進行資料分析時,確保使用正確的資料型別非常重要,否則可能會得到意想不到的結果或錯誤。對 Pandas 而言,它會在很多情況下正確地作出資料型別推斷,你可以繼續進行分析工作,而無需深入思考該主題。
儘管 Pandas 工作得很好,但在資料分析過程中的某個時刻,你可能需要將資料從一種型別顯式轉換為另一種型別。本文將討論 Pandas 的基本資料型別(即 dtypes
),它們如何對映到 python 和 numpy 資料型別,以及從一種 Pandas 型別轉換為另一種型別的幾個方式。
Pandas 的資料型別
資料型別本質上是程式語言用來理解如何儲存和運算元據的內部結構。例如,一個程式需要理解你可以將兩個數字加起來,比如 5 + 10 得到 15。或者,如果是兩個字串,比如「cat」和「hat」,你可以將它們連線(加)起來得到「cathat」。
有關 Pandas 資料型別的一個可能令人困惑的地方是,Pandas、Python 和 numpy 的資料型別之間有一些重疊。下表總結了關鍵點:
Pandas dtype
對映:
Pandas dtype | Python 型別 | NumPy 型別 | 用途 |
---|---|---|---|
object | str | string_, unicode_ | 文字 |
int64 | int | int_, int8, int16, int32, int64, uint8, uint16, uint32, uint64 | 整數 |
float64 | float | float_, float16, float32, float64 | 浮點數 |
bool | bool | bool_ | 布林值 |
datetime64 | NA | NA | 日期時間 |
timedelta[ns] | NA | NA | 時間差 |
category | NA | NA | 有限長度的文字值列表 |
大多數情況下,你不必擔心是否應該明確地將熊貓型別強制轉換為對應的 NumPy 型別。一般來說使用 Pandas 的預設 int64
和 float64
就可以。我列出此表的唯一原因是,有時你可能會在程式碼行間或自己的分析過程中看到 Numpy 的型別。
對於本文,我將重點關注以下 Pandas 型別:
object
int64
float64
datetime64
bool
如果你有興趣,我會再寫一篇文章來專門介紹 category
和 timedelta
型別。不過本文中概述的基本方法也適用於這些型別。
我們為什麼關心型別?
資料型別是在你遇到錯誤或意外結果之前並不會關心的事情之一。不過當你將新資料載入到 Pandas 進行進一步分析時,這也是你應該檢查的第一件事情。
我將使用一個非常簡單的 CSV檔案 來說明在 Pandas 中可能會遇到的一些常見的由資料型別導致的錯誤。另外,在 github 上也一個示例 notbook。
import numpy as np
import pandas as pd
df = pd.read_csv("sales_data_types.csv")
複製程式碼
Customer Number | Customer Name | 2016 | 2017 | Percent Growth | Jan Units | Month | Day | Year | Active | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 10002.0 | Quest Industries | $125,000.00 | $162500.00 | 30.00% | 500 | 1 | 10 | 2015 | Y |
1 | 552278.0 | Smith Plumbing | $920,000.00 | $101,2000.00 | 10.00% | 700 | 6 | 15 | 2014 | Y |
2 | 23477.0 | ACME Industrial | $50,000.00 | $62500.00 | 25.00% | 125 | 3 | 29 | 2016 | Y |
3 | 24900.0 | Brekke LTD | $350,000.00 | $490000.00 | 4.00% | 75 | 10 | 27 | 2015 | Y |
4 | 651029.0 | Harbor Co | $15,000.00 | $12750.00 | -15.00% | Closed | 2 | 2 | 2014 | N |
乍一看,資料沒什麼問題,所以我們可以嘗試做一些操作來分析資料。我們來試一下把 2016 和 2017 年的銷售額加起來:
df[`2016`] + df[`2017`]
複製程式碼
0 $125,000.00$162500.00
1 $920,000.00$101,2000.00
2 $50,000.00$62500.00
3 $350,000.00$490000.00
4 $15,000.00$12750.00
dtype: object
複製程式碼
這看起來就不對了。我們希望將總計加在一起,但 Pandas 只是將這兩個值連線在一起建立了一個長字串。這個問題的一個線索是 dtype:object
。object
在 Pandas 代表字串,所以它執行的是字串操作而不是數學操作。
如果我們想檢視 dataframe 中的所有資料型別,使用 df.dtypes
df.dtypes
複製程式碼
Customer Number float64
Customer Name object
2016 object
2017 object
Percent Growth object
Jan Units object
Month int64
Day int64
Year int64
Active object
dtype: object
複製程式碼
此外,df.info()
函式可以顯示更有用的資訊。
df.info()
複製程式碼
<class `pandas.core.frame.DataFrame`>
RangeIndex: 5 entries, 0 to 4
Data columns (total 10 columns):
Customer Number 5 non-null float64
Customer Name 5 non-null object
2016 5 non-null object
2017 5 non-null object
Percent Growth 5 non-null object
Jan Units 5 non-null object
Month 5 non-null int64
Day 5 non-null int64
Year 5 non-null int64
Active 5 non-null object
dtypes: float64(1), int64(3), object(6)
memory usage: 480.0+ bytes
複製程式碼
檢視自動分配的資料型別後,有幾個問題:
Customer Number
被歸為float64
但它應該是int64
2016
和2017
這兩列被儲存為object
,但應該是float64
或int64
這樣的數值型別Percent Growth
和Jan Units
也被儲存為object
而不是數值型別Month
、Day
和Year
這三列應該被轉換為datetime64
Active
列應該是布林型
在我們清洗這些資料型別之前,要對這些資料做更多的附加分析是非常困難的。
為了在 Pandas 中轉換資料型別,有三個基本選項:
- 使用
astype()
來強制轉換到合適的dtype
- 建立一個自定義函式來轉換資料
- 使用 Pandas 的函式,例如
to_numeric()
或to_datetime()
使用 astype()
函式
將 Pandas 資料列轉換為不同型別的最簡單方法就是用 astype()
。例如,要將 Customer Number
轉換為整數,我們可以這樣呼叫:
df[`Customer Number`].astype(`int`)
複製程式碼
0 10002
1 552278
2 23477
3 24900
4 651029
Name: Customer Number, dtype: int64
複製程式碼
為了真正修改原始 dataframe 中的客戶編號(Customer Number),記得把 astype()
函式的返回值重新賦值給 dataframe,因為 astype()
僅返回資料的副本而不原地修改。
df["Customer Number"] = df[`Customer Number`].astype(`int`)
df.dtypes
複製程式碼
Customer Number int64
Customer Name object
2016 object
2017 object
Percent Growth object
Jan Units object
Month int64
Day int64
Year int64
Active object
dtype: object
複製程式碼
以下是客戶編號(Customer Number)為整數的新 dataframe:
Customer Number | Customer Name | 2016 | 2017 | Percent Growth | Jan Units | Month | Day | Year | Active | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 10002 | Quest Industries | $125,000.00 | $162500.00 | 30.00% | 500 | 1 | 10 | 2015 | Y |
1 | 552278 | Smith Plumbing | $920,000.00 | $101,2000.00 | 10.00% | 700 | 6 | 15 | 2014 | Y |
2 | 23477 | ACME Industrial | $50,000.00 | $62500.00 | 25.00% | 125 | 3 | 29 | 2016 | Y |
3 | 24900 | Brekke LTD | $350,000.00 | $490000.00 | 4.00% | 75 | 10 | 27 | 2015 | Y |
4 | 651029 | Harbor Co | $15,000.00 | $12750.00 | -15.00% | Closed | 2 | 2 | 2014 | N |
這一切看起來不錯,並且似乎很簡單。讓我們嘗試對 2016
列做同樣的事情並將其轉換為浮點數:
df[`2016`].astype(`float`)
複製程式碼
ValueError Traceback (most recent call last)
<ipython-input-45-999869d577b0> in <module>()
----> 1 df[`2016`].astype(`float`)
[一些錯誤資訊]
ValueError: could not convert string to float: `$15,000.00`
複製程式碼
以類似的方式,我們可以嘗試將 Jan Units
列轉換為整數:
df[`Jan Units`].astype(`int`)
複製程式碼
ValueError Traceback (most recent call last)
<ipython-input-44-31333711e4a4> in <module>()
----> 1 df[`Jan Units`].astype(`int`)
[一些錯誤資訊]
ValueError: invalid literal for int() with base 10: `Closed`
複製程式碼
這兩個都返回 ValueError
異常,這意味著轉換不起作用。
在這兩個例子中,資料都包含不能被解釋為數字的值。在銷售額(2016)列中,資料包括貨幣符號以及每個值中的逗號。在 Jan Units
列中,最後一個值是 “Closed”,它不是一個數字;所以我們得到了異常。
到目前為止,astype()
作為工具看起來並不怎麼好。我們在 Active
列中再試一次。
df[`Active`].astype(`bool`)
複製程式碼
0 True
1 True
2 True
3 True
4 True
Name: Active, dtype: bool
複製程式碼
第一眼,這看起來不錯,但經過仔細檢查,存在一個大問題。所有的值都被解釋為 True
,但最後一個客戶有一個 N
的活動(Active)標誌,所以這並不正確。
這一節的重點是 astype()
只有在下列情況下才能工作:
- 資料是乾淨的,可以簡單地解釋為一個數字
- 你想要將一個數值轉換為一個字串物件
如果資料具有非數字字元或它們間不同質(homogeneous),那麼 astype()
並不是型別轉換的好選擇。你需要進行額外的變換才能完成正確的型別轉換。
自定義轉換函式
由於該資料轉換稍微複雜一些,因此我們可以構建一個自定義函式,將其應用於每個值並轉換為適當的資料型別。
對於貨幣轉換(這個特定的資料集),下面是一個我們可以使用的簡單函式:
def convert_currency(val):
"""
Convert the string number value to a float
- Remove $
- Remove commas
- Convert to float type
"""
new_val = val.replace(`,`,``).replace(`$`, ``)
return float(new_val)
複製程式碼
該程式碼使用python的字串函式去掉 $
和 ,
,然後將該值轉換為浮點數。在這個特定情況下,我們可以將值轉換為整數,但我選擇在這種情況下使用浮點數。
我也懷疑有人會建議我們對貨幣使用 Decimal
型別。這不是 Pandas 的本地資料型別,所以我故意堅持使用 float 方式。
另外值得注意的是,該函式將數字轉換為 python 的 float
,但 Pandas 內部將其轉換為 float64
。正如前面提到的,我建議你允許 Pandas 在確定合適的時候將其轉換為特定的大小 float
或 int
。你不需要嘗試將其轉換為更小或更大的位元組大小,除非你真的知道為什麼需要那樣做。
現在,我們可以使用 Pandas 的 apply
函式將其應用於 2016 列中的所有值。
df[`2016`].apply(convert_currency)
複製程式碼
0 125000.0
1 920000.0
2 50000.0
3 350000.0
4 15000.0
Name: 2016, dtype: float64
複製程式碼
成功!所有的值都顯示為 float64
,我們可以完成所需要的所有數學計算了。
我確信有經驗的讀者會問為什麼我不使用 lambda 函式?在回答之前,先看下我們可以在一行中使用 lambda
函式完成的操作:
df[`2016`].apply(lambda x: x.replace(`$`, ``).replace(`,`, ``)).astype(`float`)
複製程式碼
使用 lambda
,我們可以將程式碼簡化為一行,這是非常有效的方法。但我對這種方法有三個主要的意見:
- 如果你只是在學習 Python / Pandas,或者如果將來會有 Python 新人來維護程式碼,我認為更長的函式的可讀性更好。主要原因是它可以包含註釋,也可以分解為若干步驟。lambda 函式對於新手來說更難以掌握。
- 其次,如果你打算在多個列上重複使用這個函式,複製長長的 lambda 函式並不方便。
- 最後,使用函式可以在使用
read_csv()
時輕鬆清洗資料。我將在文章結尾處介紹具體的使用方法。
有些人也可能會爭辯說,其他基於 lambda 的方法比自定義函式的效能有所提高。但為了教導新手,我認為函式方法更好。
以下是使用 convert_currency
函式轉換兩個銷售(2016 / 2017)列中資料的完整示例。
df[`2016`] = df[`2016`].apply(convert_currency)
df[`2017`] = df[`2017`].apply(convert_currency)
df.dtypes
複製程式碼
Customer Number int64
Customer Name object
2016 float64
2017 float64
Percent Growth object
Jan Units object
Month int64
Day int64
Year int64
Active object
dtype: object
複製程式碼
有關使用 lambda
和函式的另一個例子,我們可以看看修復 Percent Growth
列的過程。
使用 lambda
:
df[`Percent Growth`].apply(lambda x: x.replace(`%`, ``)).astype(`float`) / 100
複製程式碼
用自定義函式做同樣的事情:
def convert_percent(val):
"""
Convert the percentage string to an actual floating point percent
- Remove %
- Divide by 100 to make decimal
"""
new_val = val.replace(`%`, ``)
return float(new_val) / 100
df[`Percent Growth`].apply(convert_percent)
複製程式碼
兩者返回的值相同:
0 0.30
1 0.10
2 0.25
3 0.04
4 -0.15
Name: Percent Growth, dtype: float64
複製程式碼
我將介紹的最後一個自定義函式是使用 np.where()
將活動(Active)列轉換為布林值。有很多方法來解決這個特定的問題。np.where()
方法對於很多型別的問題都很有用,所以我選擇在這裡介紹它。
其基本思想是使用 np.where()
函式將所有 Y
值轉換為 True
,其他所有值為 False
df["Active"] = np.where(df["Active"] == "Y", True, False)
複製程式碼
其結果如下 dataframe:
Customer Number | Customer Name | 2016 | 2017 | Percent Growth | Jan Units | Month | Day | Year | Active | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 10002.0 | Quest Industries | $125,000.00 | $162500.00 | 30.00% | 500 | 1 | 10 | 2015 | True |
1 | 552278.0 | Smith Plumbing | $920,000.00 | $101,2000.00 | 10.00% | 700 | 6 | 15 | 2014 | True |
2 | 23477.0 | ACME Industrial | $50,000.00 | $62500.00 | 25.00% | 125 | 3 | 29 | 2016 | True |
3 | 24900.0 | Brekke LTD | $350,000.00 | $490000.00 | 4.00% | 75 | 10 | 27 | 2015 | True |
4 | 651029.0 | Harbor Co | $15,000.00 | $12750.00 | -15.00% | Closed | 2 | 2 | 2014 | False |
dtype 被正確地設定為了 bool
。
df.dtypes
複製程式碼
Customer Number float64
Customer Name object
2016 object
2017 object
Percent Growth object
Jan Units object
Month int64
Day int64
Year int64
Active bool
dtype: object
複製程式碼
無論你選擇使用 lambda
函式,還是建立一個更標準的 Python 函式,或者是使用其他方法(如 np.where
),這些方法都非常靈活,並且可以根據你自己獨特的資料需求進行定製。
Pandas 輔助函式
Pandas 在直白的 astype()
函式和複雜的自定義函式之間有一箇中間地帶。這些輔助函式對於某些資料型別轉換非常有用。
如果你順序讀下來,你會注意到我沒有對日期列或 Jan Units
列做任何事情。這兩種列都可以使用 Pandas 的內建函式(如 pd.to_numeric()
和 pd.to_datetime()
)進行轉換。
Jan Units
轉換出現問題的原因是列中包含一個非數字值。如果我們嘗試使用 astype()
,我們會得到一個錯誤(如前所述)。pd.to_numeric()
函式可以更優雅地處理這些值:
pd.to_numeric(df[`Jan Units`], errors=`coerce`)
複製程式碼
0 500.0
1 700.0
2 125.0
3 75.0
4 NaN
Name: Jan Units, dtype: float64
複製程式碼
這裡有幾個值得注意的地方。首先,該函式輕鬆地處理了資料並建立了一個 float64
列。 此外,它會用 NaN
值替換無效的 Closed
值,因為我們配置了 errors=coerce
。我們可以將 Nan
留在那裡,也可以使用 fillna(0)
來用 0 填充:
pd.to_numeric(df[`Jan Units`], errors=`coerce`).fillna(0)
複製程式碼
0 500.0
1 700.0
2 125.0
3 75.0
4 0.0
Name: Jan Units, dtype: float64
複製程式碼
我最終介紹的轉換是將單獨的月份、日期和年份列轉換為到一個 datetime
型別的列。Pandas 的 pd.to_datetime()
函式 可定製性很好,但預設情況下也十分明智。
pd.to_datetime(df[[`Month`, `Day`, `Year`]])
複製程式碼
0 2015-01-10
1 2014-06-15
2 2016-03-29
3 2015-10-27
4 2014-02-02
dtype: datetime64[ns]
複製程式碼
在這種情況下,函式將這些列組合成適當 datateime64
dtype 的新列。
我們需要確保將這些值賦值回 dataframe:
df["Start_Date"] = pd.to_datetime(df[[`Month`, `Day`, `Year`]])
df["Jan Units"] = pd.to_numeric(df[`Jan Units`], errors=`coerce`).fillna(0)
複製程式碼
Customer Number | Customer Name | 2016 | 2017 | Percent Growth | Jan Units | Month | Day | Year | Active | Start_Date | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 10002 | Quest Industries | 125000.0 | 162500.0 | 0.30 | 500.0 | 1 | 10 | 2015 | True | 2015-01-10 |
1 | 552278 | Smith Plumbing | 920000.0 | 1012000.0 | 0.10 | 700.0 | 6 | 15 | 2014 | True | 2014-06-15 |
2 | 23477 | ACME Industrial | 50000.0 | 62500.0 | 0.25 | 125.0 | 3 | 29 | 2016 | True | 2016-03-29 |
3 | 24900 | Brekke LTD | 350000.0 | 490000.0 | 0.04 | 75.0 | 10 | 27 | 2015 | True | 2015-10-27 |
4 | 651029 | Harbor Co | 15000.0 | 12750.0 | -0.15 | NaN | 2 | 2 | 2014 | False | 2014-02-02 |
現在資料已正確轉換為我們需要的所有型別:
df.dtypes
複製程式碼
Customer Number int64
Customer Name object
2016 float64
2017 float64
Percent Growth float64
Jan Units float64
Month int64
Day int64
Year int64
Active bool
Start_Date datetime64[ns]
複製程式碼
Dataframe 已準備好進行分析!
把它們放在一起
在資料採集過程中應該儘早地使用 astype()
和自定義轉換函式。如果你有一個打算重複處理的資料檔案,並且它總是以相同的格式儲存,你可以定義在讀取資料時需要應用的 dtype
和 converters
。將 dtype
視為對資料執行 astype()
很有幫助。converters
引數允許你將函式應用到各種輸入列,類似於上面介紹的方法。
需要注意的是,只能使用 dtype
或 converter
函式中的一種來應用於指定的列。如果你嘗試將兩者應用於同一列,則會跳過 dtype
。
下面是一個簡化的例子,它在資料讀入 dataframe 時完成幾乎所有的轉換:
df_2 = pd.read_csv("sales_data_types.csv",
dtype={`Customer Number`: `int`},
converters={`2016`: convert_currency,
`2017`: convert_currency,
`Percent Growth`: convert_percent,
`Jan Units`: lambda x: pd.to_numeric(x, errors=`coerce`),
`Active`: lambda x: np.where(x == "Y", True, False)
})
df_2.dtypes
複製程式碼
Customer Number int64
Customer Name object
2016 float64
2017 float64
Percent Growth float64
Jan Units float64
Month int64
Day int64
Year int64
Active object
dtype: object
複製程式碼
正如前面提到的,我選擇了包含用於轉換資料的 lambda
示例和函式示例。唯一無法被應用在這裡的函式就是那個用來將 Month
、Day
和 Year
三列轉換到 datetime
列的函式。不過,這仍是一個強大的可以幫助改進資料處理流程的約定。
總結
探索新資料集的第一步是確保資料型別設定正確。大部分時間 Pandas 都會做出合理推論,但資料集中有很多細微差別,因此知道如何使用 Pandas 中的各種資料轉換選項非常重要。如果你有任何其他建議,或者有興趣探索 category
資料型別,請隨時在下面發表評論。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。