[譯] Pandas 資料型別概覽

stormluke發表於2019-03-04
article header image

簡介

在進行資料分析時,確保使用正確的資料型別非常重要,否則可能會得到意想不到的結果或錯誤。對 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 的預設 int64float64 就可以。我列出此表的唯一原因是,有時你可能會在程式碼行間或自己的分析過程中看到 Numpy 的型別。

對於本文,我將重點關注以下 Pandas 型別:

  • object
  • int64
  • float64
  • datetime64
  • bool

如果你有興趣,我會再寫一篇文章來專門介紹 categorytimedelta 型別。不過本文中概述的基本方法也適用於這些型別。

我們為什麼關心型別?

資料型別是在你遇到錯誤或意外結果之前並不會關心的事情之一。不過當你將新資料載入到 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:objectobject 在 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
  • 20162017 這兩列被儲存為 object,但應該是 float64int64 這樣的數值型別
  • Percent GrowthJan Units 也被儲存為 object 而不是數值型別
  • MonthDayYear 這三列應該被轉換為 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 在確定合適的時候將其轉換為特定的大小 floatint。你不需要嘗試將其轉換為更小或更大的位元組大小,除非你真的知道為什麼需要那樣做。

現在,我們可以使用 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() 和自定義轉換函式。如果你有一個打算重複處理的資料檔案,並且它總是以相同的格式儲存,你可以定義在讀取資料時需要應用的 dtypeconverters。將 dtype 視為對資料執行 astype() 很有幫助。converters 引數允許你將函式應用到各種輸入列,類似於上面介紹的方法。

需要注意的是,只能使用 dtypeconverter 函式中的一種來應用於指定的列。如果你嘗試將兩者應用於同一列,則會跳過 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 示例和函式示例。唯一無法被應用在這裡的函式就是那個用來將 MonthDayYear 三列轉換到 datetime 列的函式。不過,這仍是一個強大的可以幫助改進資料處理流程的約定。

總結

探索新資料集的第一步是確保資料型別設定正確。大部分時間 Pandas 都會做出合理推論,但資料集中有很多細微差別,因此知道如何使用 Pandas 中的各種資料轉換選項非常重要。如果你有任何其他建議,或者有興趣探索 category 資料型別,請隨時在下面發表評論。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章