[譯] Pandas 數據類型概覽

article header image

簡介

在進行數據分析時,確保使用正確的數據類型很是重要,不然可能會獲得意想不到的結果或錯誤。對 Pandas 而言,它會在不少狀況下正確地做出數據類型推斷,你能夠繼續進行分析工做,而無需深刻思考該主題。html

儘管 Pandas 工做得很好,但在數據分析過程當中的某個時刻,你可能須要將數據從一種類型顯式轉換爲另外一種類型。本文將討論 Pandas 的基本數據類型(即 dtypes),它們如何映射到 python 和 numpy 數據類型,以及從一種 Pandas 類型轉換爲另外一種類型的幾個方式。前端

Pandas 的數據類型

數據類型本質上是編程語言用來理解如何存儲和操做數據的內部結構。例如,一個程序須要理解你能夠將兩個數字加起來,好比 5 + 10 獲得 15。或者,若是是兩個字符串,好比「cat」和「hat」,你能夠將它們鏈接(加)起來獲得「cathat」。python

有關 Pandas 數據類型的一個可能使人困惑的地方是,Pandas、Python 和 numpy 的數據類型之間有一些重疊。下表總結了關鍵點:android

Pandas dtype 映射:ios

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 的類型。git

對於本文,我將重點關注如下 Pandas 類型:github

  • object
  • int64
  • float64
  • datetime64
  • bool

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

咱們爲何關心類型?

數據類型是在你遇到錯誤或意外結果以前並不會關心的事情之一。不過當你將新數據加載到 Pandas 進行進一步分析時,這也是你應該檢查的第一件事情。後端

我將使用一個很是簡單的 CSV文件 來講明在 Pandas 中可能會遇到的一些常見的由數據類型致使的錯誤。另外,在 github 上也一個示例 notbookbash

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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索