[譯] 使用 NumPy 和 Pandas 進行 Python 式資料清理

Bamboo發表於2018-04-17

使用 NumPy 和 Pandas 進行 Python 式資料清理

[譯] 使用 NumPy 和 Pandas 進行 Python 式資料清理

資料科學家花費大量時間清理資料集,將它們清理為可以工作的形式。事實上,很多資料科學家表示,80% 的工作都是獲取和清理資料。

因此,不管你是剛剛進入這個領域或者計劃進入,那麼處理混亂資料的能力會非常重要,無論這意味著缺失值、格式不一致、格式錯誤還是無意義的異常值。

在此教程中,我們將利用 Pandas 和 NumPy 這兩個庫來清理資料。

我們將介紹以下內容:

  • 刪除 DataFrame 中不必要的列
  • 更改 DataFrame 的索引
  • .str() 方法清理列
  • 使用 DataFrame.applymap() 函式以元素方式清理資料集
  • 將列重新命名為更易識別的標籤
  • 跳過 CSV 檔案中不必要的行

這些是我們將要用到的資料集:

你可以從 Real Python 的 GitHub 倉庫 下載所有資料集,以便檢視以下示例。

注意:我推薦使用 Jupyter Notebook 來進行以下步驟。

本教程假設你對 Pandas 和 NumPy 庫有基本的瞭解,包括 Pandas 的主要工作物件 SeriesDataFrame,應用於它們的常用方法,以及熟悉 NumPy 的 NaN 值。

讓我們從 import 這些模組開始吧!

>>> import pandas as pd
>>> import numpy as np
複製程式碼

刪除 DataFrame 中不必要的列

你經常會發現資料集中並非所有類別的資料都對你有用。例如,你可能有一個資料集包含了學生資訊(名字、成績、標準、父母姓名和住址),但你想要專注於分析學生的成績。

在這種情況下,住址和父母姓名對你來說並不重要,保留這些類別將佔用不必要的空間,並可能拖累執行時間。

Pandas 提供了一個很方便的 drop() 函式來從 DataFrame 中移除列或行。我們來看一個簡單的例子,從 DataFrame 中刪除一些列。

首先,我們從 CSV 檔案 “BL-Flickr-Images-Book.csv” 中建立一個 DataFrame。在下面的例子中,我們把相對路徑傳遞給 pd.read_csv,當前工作路徑下,所有的資料集都存放在 Datasets 資料夾中:

>>> df = pd.read_csv('Datasets/BL-Flickr-Images-Book.csv')
>>> df.head()

    Identifier             Edition Statement      Place of Publication  \
0         206                           NaN                    London
1         216                           NaN  London; Virtue & Yorston
2         218                           NaN                    London
3         472                           NaN                    London
4         480  A new edition, revised, etc.                    London

  Date of Publication              Publisher  \
0         1879 [1878]       S. Tinsley & Co.
1                1868           Virtue & Co.
2                1869  Bradbury, Evans & Co.
3                1851          James Darling
4                1857   Wertheim & Macintosh

                                               Title     Author  \
0                  Walter Forbes. [A novel.] By A. A      A. A.
1  All for Greed. [A novel. The dedication signed...  A., A. A.
2  Love the Avenger. By the author of “All for Gr...  A., A. A.
3  Welsh Sketches, chiefly ecclesiastical, to the...  A., E. S.
4  [The World in which I live, and my place in it...  A., E. S.

                                   Contributors  Corporate Author  \
0                               FORBES, Walter.               NaN
1  BLAZE DE BURY, Marie Pauline Rose - Baroness               NaN
2  BLAZE DE BURY, Marie Pauline Rose - Baroness               NaN
3                   Appleyard, Ernest Silvanus.               NaN
4                           BROOME, John Henry.               NaN

   Corporate Contributors Former owner  Engraver Issuance type  \
0                     NaN          NaN       NaN   monographic
1                     NaN          NaN       NaN   monographic
2                     NaN          NaN       NaN   monographic
3                     NaN          NaN       NaN   monographic
4                     NaN          NaN       NaN   monographic

                                          Flickr URL  \
0  http://www.flickr.com/photos/britishlibrary/ta...
1  http://www.flickr.com/photos/britishlibrary/ta...
2  http://www.flickr.com/photos/britishlibrary/ta...
3  http://www.flickr.com/photos/britishlibrary/ta...
4  http://www.flickr.com/photos/britishlibrary/ta...

                            Shelfmarks
0    British Library HMNTS 12641.b.30.
1    British Library HMNTS 12626.cc.2.
2    British Library HMNTS 12625.dd.1.
3    British Library HMNTS 10369.bbb.15.
4    British Library HMNTS 9007.d.28.
複製程式碼

當我們使用 head() 方法檢視前五條資料時,我們可以看到一些列提供了對圖書館來說有用的輔助資訊,但是對描述書籍本身並沒有太多幫助: Edition StatementCorporate AuthorCorporate ContributorsFormer ownerEngraverIssuance typeShelfmarks

我們可以這樣刪除這些列:

>>> to_drop = ['Edition Statement',
...            'Corporate Author',
...            'Corporate Contributors',
...            'Former owner',
...            'Engraver',
...            'Contributors',
...            'Issuance type',
...            'Shelfmarks']

>>> df.drop(to_drop, inplace=True, axis=1)
複製程式碼

這裡,我們定義了一個列表,其中包含了我們想要刪除的列的名字。然後呼叫 drop() 函式,傳入 inplace 引數為 True,以及 axis 引數為 1。這兩個引數告訴 Pandas 我們想要讓改變直接作用在物件上,並且我們需要刪除的是列。

再次檢視 DataFrame,可以發現不想要的列已經被移除了:

>>> df.head()
   Identifier      Place of Publication Date of Publication  \
0         206                    London         1879 [1878]
1         216  London; Virtue & Yorston                1868
2         218                    London                1869
3         472                    London                1851
4         480                    London                1857

               Publisher                                              Title  \
0       S. Tinsley & Co.                  Walter Forbes. [A novel.] By A. A
1           Virtue & Co.  All for Greed. [A novel. The dedication signed...
2  Bradbury, Evans & Co.  Love the Avenger. By the author of “All for Gr...
3          James Darling  Welsh Sketches, chiefly ecclesiastical, to the...
4   Wertheim & Macintosh  [The World in which I live, and my place in it...

      Author                                         Flickr URL
0      A. A.  http://www.flickr.com/photos/britishlibrary/ta...
1  A., A. A.  http://www.flickr.com/photos/britishlibrary/ta...
2  A., A. A.  http://www.flickr.com/photos/britishlibrary/ta...
3  A., E. S.  http://www.flickr.com/photos/britishlibrary/ta...
4  A., E. S.  http://www.flickr.com/photos/britishlibrary/ta...
複製程式碼

或者,我們可以通過直接將列傳遞給 columns 引數來刪除列,不用單獨指定刪除的標籤以及刪除列還是行:

>>> df.drop(columns=to_drop, inplace=True)
複製程式碼

這種方法更直觀易讀,這一步做了什麼是非常明顯的。

如果你事先知道那些列是你需要保留的,另外一個選擇是將列作為 usecols 的引數傳給 pd.read_csv

更改 DataFrame 的索引

Pandas 的 Index 擴充套件了 NumPy 的陣列功能,從而可以實現更多功能的擷取和標籤。在多數情況下,使用資料唯一有價值的標識欄位作為索引是很有幫助的。

例如,在上一節使用的資料集中,可以想象到,圖書管理員如果需要搜尋記錄,他也許輸入的是書籍的唯一識別符號(Identifier 列):

>>> df['Identifier'].is_unique
True
複製程式碼

讓我們用 set_index 來替換現有的索引:

>>> df = df.set_index('Identifier')
>>> df.head()
                Place of Publication Date of Publication  \
206                           London         1879 [1878]
216         London; Virtue & Yorston                1868
218                           London                1869
472                           London                1851
480                           London                1857

                        Publisher  \
206              S. Tinsley & Co.
216                  Virtue & Co.
218         Bradbury, Evans & Co.
472                 James Darling
480          Wertheim & Macintosh

                                                        Title     Author  \
206                         Walter Forbes. [A novel.] By A. A      A. A.
216         All for Greed. [A novel. The dedication signed...  A., A. A.
218         Love the Avenger. By the author of “All for Gr...  A., A. A.
472         Welsh Sketches, chiefly ecclesiastical, to the...  A., E. S.
480         [The World in which I live, and my place in it...  A., E. S.

                                                   Flickr URL
206         http://www.flickr.com/photos/britishlibrary/ta...
216         http://www.flickr.com/photos/britishlibrary/ta...
218         http://www.flickr.com/photos/britishlibrary/ta...
472         http://www.flickr.com/photos/britishlibrary/ta...
480         http://www.flickr.com/photos/britishlibrary/ta...
複製程式碼

技術細節: 與 SQL 中的主鍵不同,Pandas 的 Index 不保證是唯一的,儘管許多索引及合併操作在唯一的情況下執行時會加速。

我們可以使用 loc[] 直接訪問每條記錄。儘管 loc[] 可能不具有直觀的名稱,但它允許我們執行基於標籤的索引,即標記某一行或某一條記錄而不用考慮其位置:

>>> df.loc[206]
Place of Publication                                               London
Date of Publication                                           1879 [1878]
Publisher                                                S. Tinsley & Co.
Title                                   Walter Forbes. [A novel.] By A. A
Author                                                              A. A.
Flickr URL              http://www.flickr.com/photos/britishlibrary/ta...
Name: 206, dtype: object
複製程式碼

換句話說,206 是索引的第一個標籤。如要按位置訪問它,我們可以使用 df.iloc[0],它執行基於位置的索引。

技術細節.loc[] 在技術上來說是一個類例項,它有一些特殊的語法不完全符合大多數普通 Python 例項方法。

一開始,我們的索引是一個 RangeIndex,也就是從 0 開始的整數,類似於 Python 內建的 range。通過把列的名稱傳給 set_index,我們將索引改成了 Identifier 中的值。

你可能注意到,我們使用 df = df.set_index(...) 將此方法返回的值重新賦值給變數。這是因為預設情況下,此方法會返回一個修改後的副本,並不會直接對原本的物件進行更改,索引可以通過設定 inplace 引數來避免這種情況:

df.set_index('Identifier', inplace=True)
複製程式碼

整理資料中的欄位

到這裡,我們已經刪除了不必要的列,並將 DataFrame 的索引更改為更有意義的列。在這一節,我們將會清理特定的列,使其成為統一的格式,以便更好地理解資料集並強化一致性。具體來說,我們將清理 Date of PublicationPlace of Publication 這兩列。

經過檢查,所有的資料型別都是 object dtype,它與 Python 中的 str 類似。

它封裝了任何不適用於數字或分類資料的欄位。這是有道理的,因為我們使用的資料最初只是一堆雜亂的字元:

>>> df.get_dtype_counts()
object    6
複製程式碼

其中出版日期一列,如果將其轉化為數字型別更有意義,所以我們可以進行如下計算:

>>> df.loc[1905:, 'Date of Publication'].head(10)
Identifier
1905           1888
1929    1839, 38-54
2836        [1897?]
2854           1865
2956        1860-63
2957           1873
3017           1866
3131           1899
4598           1814
4884           1820
Name: Date of Publication, dtype: object
複製程式碼

一本書只能有一個出版日期,因此我們需要做到以下幾點:

  • 除去方括號內的多餘日期,不管出現在哪裡,例如:1879 [1878]
  • 將日期範圍轉換為“開始日期”,例如:1860-63; 1839, 38-54
  • 完全移除任何不確定的日期,並用 NumPy 的 NaN 值替代:[1897?]
  • 將字串 nan 也轉換為 NumPy 的 NaN

綜合以上,我們實際上可以利用一個正規表示式來提取出版年份:

regex = r'^(\d{4})'
複製程式碼

這個正規表示式意圖在字串的開頭找到四位數字,這足以滿足我們的要求。上面是一個原始字串(這意味著反斜槓不再是轉義字元),這是正規表示式的標準做法。

\d 表示任何數字,{4} 表示重複 4 次,^ 表示匹配字串的開頭,括號表示一個捕獲組,它向 Pandas 表明我們想要提取正規表示式的這部分。(我們希望用 ^ 來避免字串從 [ 開始的情況。)

現在讓我們來看看我們在資料集中執行這個表示式時會發生什麼:

>>> extr = df['Date of Publication'].str.extract(r'^(\d{4})', expand=False)
>>> extr.head()
Identifier
206    1879
216    1868
218    1869
472    1851
480    1857
Name: Date of Publication, dtype: object
複製程式碼

對正則不熟悉?你可以在 regex101.com 這個網站上檢視上面這個正規表示式,也可以閱讀更多 Python 正規表示式 HOWTO 上的教程。

從技術上講,這一列仍然是 object dtype,但是我們用 pd.to_numeric 即可輕鬆獲取數字:

>>> df['Date of Publication'] = pd.to_numeric(extr)
>>> df['Date of Publication'].dtype
dtype('float64')
複製程式碼

這麼做會導致十分之一的值丟失,但這相對於能夠對剩餘的有效值上進行計算而已,是一個比較小的代價:

>>> df['Date of Publication'].isnull().sum() / len(df)
0.11717147339205986
複製程式碼

很好!本節完成了!

結合 NumPy 以及 str 方法來清理列

上一部分,你可能已經注意到我們使用了 df['Date of Publication'].str。這個屬性是訪問 Pandas 的快速字串操作的一種方式,它主要模仿了原生 Python 中的字串或編譯的正規表示式方法,例如 .split().replace().capitalize()

為了清理 Place of Publication 欄位,我們可以結合 Pandas 的 str 方法以及 NumPy 的 np.where 函式,這個函式基本上是 Excel 裡的 IF() 巨集的向量化形式。它的語法如下:

>>> np.where(condition, then, else)
複製程式碼

這裡,condition 可以是一個類似陣列的物件或者一個布林遮罩,如果 conditionTrue,則使用 then 值,否則使用 else 值。

從本質上來說,.where() 函式對物件中的每個元素進行檢查,看 condition 是否為 True,並返回一個 ndarray 物件,包含then 或者 else 的值。

它也可以被用於巢狀的 if-then 語句中,允許我們根據多個條件進行計算:

>>> np.where(condition1, x1, 
        np.where(condition2, x2, 
            np.where(condition3, x3, ...)))
複製程式碼

我們將用這兩個函式來清理 Place of Publication 一列,因為此列包含字串。以下是該列的內容:

>>> df['Place of Publication'].head(10)
Identifier
206                                  London
216                London; Virtue & Yorston
218                                  London
472                                  London
480                                  London
481                                  London
519                                  London
667     pp. 40. G. Bryan & Co: Oxford, 1898
874                                 London]
1143                                 London
Name: Place of Publication, dtype: object
複製程式碼

我們發現某些行中,出版地被其他不必要的資訊包圍著。如果觀察更多值,我們會發現只有出版地包含 ‘London’ 或者 ‘Oxford’ 的行才會出現這種情況。

我們來看看兩條特定的資料:

>>> df.loc[4157862]
Place of Publication                                  Newcastle-upon-Tyne
Date of Publication                                                  1867
Publisher                                                      T. Fordyce
Title                   Local Records; or, Historical Register of rema...
Author                                                        T.  Fordyce
Flickr URL              http://www.flickr.com/photos/britishlibrary/ta...
Name: 4157862, dtype: object

>>> df.loc[4159587]
Place of Publication                                  Newcastle upon Tyne
Date of Publication                                                  1834
Publisher                                                Mackenzie & Dent
Title                   An historical, topographical and descriptive v...
Author                                               E. (Eneas) Mackenzie
Flickr URL              http://www.flickr.com/photos/britishlibrary/ta...
Name: 4159587, dtype: object
複製程式碼

這兩本書在用一個地方出版,但是一個地名中間包含連字元,另一個沒有。

想要一次性清理這一列,我們可以用 str.contains() 來獲得一個布林掩碼。

我們按如下方式清理此列:

>>> pub = df['Place of Publication']
>>> london = pub.str.contains('London')
>>> london[:5]
Identifier
206    True
216    True
218    True
472    True
480    True
Name: Place of Publication, dtype: bool

>>> oxford = pub.str.contains('Oxford')
複製程式碼

np.where 結合:

df['Place of Publication'] = np.where(london, 'London',
                                      np.where(oxford, 'Oxford',
                                               pub.str.replace('-', ' ')))

>>> df['Place of Publication'].head()
Identifier
206    London
216    London
218    London
472    London
480    London
Name: Place of Publication, dtype: object
複製程式碼

這裡,np.where 函式在巢狀結果中被呼叫,condition 是從 str.contains() 返回的布林值的 Series 物件。contains() 方法類似原生 Python 中內建的 in 關鍵字,它被用來查詢一個迭代器中某個實體是否出現(或者字串中是否有某子字串)。

替換的是我們想要的出版地點的字串。我們也用 str.replace() 方法將連字元替換成了空格然後重新賦值給 DataFrame 的列。

雖然這個資料集中還有很多髒資料,我們現在只討論這兩列。

讓我們來重新看看前五項,看起來比最開始的時候清晰多了:

>>> df.head()
           Place of Publication Date of Publication              Publisher  \
206                      London                1879        S. Tinsley & Co.
216                      London                1868           Virtue & Co.
218                      London                1869  Bradbury, Evans & Co.
472                      London                1851          James Darling
480                      London                1857   Wertheim & Macintosh

                                                        Title    Author  \
206                         Walter Forbes. [A novel.] By A. A        AA
216         All for Greed. [A novel. The dedication signed...   A. A A.
218         Love the Avenger. By the author of “All for Gr...   A. A A.
472         Welsh Sketches, chiefly ecclesiastical, to the...   E. S A.
480         [The World in which I live, and my place in it...   E. S A.

                                                   Flickr URL
206         http://www.flickr.com/photos/britishlibrary/ta...
216         http://www.flickr.com/photos/britishlibrary/ta...
218         http://www.flickr.com/photos/britishlibrary/ta...
472         http://www.flickr.com/photos/britishlibrary/ta...
480         http://www.flickr.com/photos/britishlibrary/ta...
複製程式碼

注意:到這裡,Place of Publication 會是一個很好轉化為 Categorical dtype 的列,因為我們可以用整數對比較小的唯一的城市進行編碼。(分類資料型別的記憶體使用量與類別數目加上資料長度成正比,dtype 物件的大小是一個常數乘以資料長度。

使用 applymap 函式清理整個資料集

在某些情況下,你會發現不僅是某一列裡有髒資料,而是分散在整個資料集。

有時如果可以對 DataFrame 裡的每個單元或元素都應用一個自定義函式會很有幫助。Pandas 的 .applymap() 函式類似內建的 map() 函式,只是它將應用於 DataFrame 中的所有元素。

讓我們來看個例子,我們將從 “university_towns.txt” 檔案中建立 DataFrame

$ head Datasets/univerisity_towns.txt
Alabama[edit]
Auburn (Auburn University)[1]
Florence (University of North Alabama)
Jacksonville (Jacksonville State University)[2]
Livingston (University of West Alabama)[2]
Montevallo (University of Montevallo)[2]
Troy (Troy University)[2]
Tuscaloosa (University of Alabama, Stillman College, Shelton State)[3][4]
Tuskegee (Tuskegee University)[5]
Alaska[edit]
複製程式碼

我們發現州名後面跟著大學城的名字這樣週期性出現:StateA TownA1 TownA2 StateB TownB1 TownB2…,如果我們在檔案中檢視州名的寫法,會發現所有都有一個 “[edit]” 子字串。

我們可以利用這個模式建立一個 (state, city) 元組列表,並將它放入 DataFrame

>>> university_towns = []
>>> with open('Datasets/university_towns.txt') as file:
...     for line in file:
...         if '[edit]' in line:
...             # Remember this `state` until the next is found
...             state = line
...         else:
...             # Otherwise, we have a city; keep `state` as last-seen
...             university_towns.append((state, line))

>>> university_towns[:5]
[('Alabama[edit]\n', 'Auburn (Auburn University)[1]\n'),
 ('Alabama[edit]\n', 'Florence (University of North Alabama)\n'),
 ('Alabama[edit]\n', 'Jacksonville (Jacksonville State University)[2]\n'),
 ('Alabama[edit]\n', 'Livingston (University of West Alabama)[2]\n'),
 ('Alabama[edit]\n', 'Montevallo (University of Montevallo)[2]\n')]
複製程式碼

我們可以將這個列表包入 DataFrame 中,並將列起名為 “State” 和 “RegionName”。Pandas 會獲取每個列表中的元素,將左邊的值放入 State 列,右邊的值放入 RegionName 列。

生成的 DataFrame 如下:

>>> towns_df = pd.DataFrame(university_towns,
...                         columns=['State', 'RegionName'])

>>> towns_df.head()
 State                                         RegionName
0  Alabama[edit]\n                    Auburn (Auburn University)[1]\n
1  Alabama[edit]\n           Florence (University of North Alabama)\n
2  Alabama[edit]\n  Jacksonville (Jacksonville State University)[2]\n
3  Alabama[edit]\n       Livingston (University of West Alabama)[2]\n
4  Alabama[edit]\n         Montevallo (University of Montevallo)[2]\n
複製程式碼

儘管我們可以使用 for 迴圈來清理上面的字串,但是使用 Pandas 會更加方便。我們只需要州名和城鎮名字,其他都可以刪除。雖然這裡也可以再次使用 .str() 方法,但我們也可以使用 applymap() 方法將一個 Python 可呼叫方法對映到 DataFrame 的每個元素上。

我們一直在使用元素這個術語,但實際上到底是指什麼呢?看一下以下這個 DataFrame 例子:

        0           1
0    Mock     Dataset
1  Python     Pandas
2    Real     Python
3   NumPy     Clean
複製程式碼

在這個例子中,每個單元格(‘Mock’、‘Dataset’、‘Python’、‘Pandas’ 等)都是一個元素。所以 applumap() 方法將函式作用於每個元素上。假設定義函式為:

>>> def get_citystate(item):
...     if ' (' in item:
...         return item[:item.find(' (')]
...     elif '[' in item:
...         return item[:item.find('[')]
...     else:
...         return item
複製程式碼

Pandas 的 .applymap() 只接受一個引數,也就是將會作用於每個元素上的函式(可呼叫):

>>> towns_df =  towns_df.applymap(get_citystate)
複製程式碼

首先,我們定義一個 Python 函式,它以 DataFrame 中的元素作為引數。在函式內部,執行元素是否包含 ([ 的檢查。

函式返回的值取決於這個檢查。最後,applymap() 函式在我們的 DataFrame 物件上被呼叫。現在我們的 DataFrame 物件更加簡潔了。

>>> towns_df.head()
     State    RegionName
0  Alabama        Auburn
1  Alabama      Florence
2  Alabama  Jacksonville
3  Alabama    Livingston
4  Alabama    Montevallo
複製程式碼

applymap() 方法從 DataFrame 中獲取每個元素,將它傳遞給函式,然後將原來的值替換為函式返回的值。就是這麼簡單!

技術細節:雖然它是一個方便多功能的方法,但 .applymap() 對於較大的資料集會有明顯的執行時間,因為它將可呼叫的 Python 函式對映到每個單獨元素。某些情況下,使用 Cython 或者 NumPy (呼叫 C 語言)裡的向量化操作更高效。

列的重新命名以及跳過行

通常,需要處理的資料集可能包含不易理解的列名,或者某些包含不重要資訊的行,它們可能是最前面的有關術語定義的幾行,或者最末尾的腳註。

在這種情況下,我們希望重新命名列以及跳過某些行,以便我們可以只對必要的資訊以及有意義的標籤進行深入分析。

為了說明我們如何做到這一點,我們先來看一看 “olympics.csv” 資料集的前五行:

$ head -n 5 Datasets/olympics.csv
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
,? Summer,01 !,02 !,03 !,Total,? Winter,01 !,02 !,03 !,Total,? Games,01 !,02 !,03 !,Combined total
Afghanistan (AFG),13,0,0,2,2,0,0,0,0,0,13,0,0,2,2
Algeria (ALG),12,5,2,8,15,3,0,0,0,0,15,5,2,8,15
Argentina (ARG),23,18,24,28,70,18,0,0,0,0,41,18,24,28,70
複製程式碼

然後,將它讀入 Pandas 的 DataFrame 中:

>>> olympics_df = pd.read_csv('Datasets/olympics.csv')
>>> olympics_df.head()
                   0         1     2     3     4      5         6     7     8  \
0                NaN  ? Summer  01 !  02 !  03 !  Total  ? Winter  01 !  02 !
1  Afghanistan (AFG)        13     0     0     2      2         0     0     0
2      Algeria (ALG)        12     5     2     8     15         3     0     0
3    Argentina (ARG)        23    18    24    28     70        18     0     0
4      Armenia (ARM)         5     1     2     9     12         6     0     0

      9     10       11    12    13    14              15
0  03 !  Total  ? Games  01 !  02 !  03 !  Combined total
1     0      0       13     0     0     2               2
2     0      0       15     5     2     8              15
3     0      0       41    18    24    28              70
4     0      0       11     1     2     9              12
複製程式碼

這確實很凌亂!列是從 0 開始索引的字串形式的數字。應該是頭部的行(也就是應該設定為列名的行)位於 olympics_df.iloc[0]。發生這種情況是因為我們的 csv 檔案是以 0、1、2…15 開頭的。

另外,如果我們去檢視資料集的來源,會發現 NaN 應該是類似 “Country”,?Summer 應該代表的是 “Summer Games”,而 01! 應該是 “Gold” 等等。

所以,我們需要做以下兩件事:

  • 跳過一行,將第一行(索引為 0)設定為 header
  • 重新命名這些列

我們可以在讀取 CSV 檔案時通過傳遞一些引數給 read_csv() 函式來跳過某行並設定 header。

這個函式有很多可選的引數,但這個情況裡,我們只需要一個引數(header)來移除第 0 行:

>>> olympics_df = pd.read_csv('Datasets/olympics.csv', header=1)
>>> olympics_df.head()
          Unnamed: 0  ? Summer  01 !  02 !  03 !  Total  ? Winter  \
0        Afghanistan (AFG)        13     0     0     2      2         0
1            Algeria (ALG)        12     5     2     8     15         3
2          Argentina (ARG)        23    18    24    28     70        18
3            Armenia (ARM)         5     1     2     9     12         6
4  Australasia (ANZ) [ANZ]         2     3     4     5     12         0

   01 !.1  02 !.1  03 !.1  Total.1  ? Games  01 !.2  02 !.2  03 !.2  \
0       0       0       0        0       13       0       0       2
1       0       0       0        0       15       5       2       8
2       0       0       0        0       41      18      24      28
3       0       0       0        0       11       1       2       9
4       0       0       0        0        2       3       4       5

   Combined total
0               2
1              15
2              70
3              12
4              12
複製程式碼

我們現在已經有了正確的 header 行,以及移除了所有不必要的行。注意 Pandas 將包含國家名字的列的名字從 NaN 變成了 Unnames:0

要重新命名列,我們將利用 rename() 方法,這個方法允許你基於一個對映(本例中,指字典)來重新標記軸的名字。

讓我們從定義一個新的字典開始,它將現在的列的名字作為 key,對映到可用性更強的名字(字典值)。

>>> new_names =  {'Unnamed: 0': 'Country',
...               '? Summer': 'Summer Olympics',
...               '01 !': 'Gold',
...               '02 !': 'Silver',
...               '03 !': 'Bronze',
...               '? Winter': 'Winter Olympics',
...               '01 !.1': 'Gold.1',
...               '02 !.1': 'Silver.1',
...               '03 !.1': 'Bronze.1',
...               '? Games': '# Games',
...               '01 !.2': 'Gold.2',
...               '02 !.2': 'Silver.2',
...               '03 !.2': 'Bronze.2'}
複製程式碼

然後呼叫 rename() 函式:

>>> olympics_df.rename(columns=new_names, inplace=True)
複製程式碼

inplace 引數設定為 True 可以將變化直接作用於我們的 DataFrame 物件上。讓我們看看是否生效:

>>> olympics_df.head()
                   Country  Summer Olympics  Gold  Silver  Bronze  Total  \
0        Afghanistan (AFG)               13     0       0       2      2
1            Algeria (ALG)               12     5       2       8     15
2          Argentina (ARG)               23    18      24      28     70
3            Armenia (ARM)                5     1       2       9     12
4  Australasia (ANZ) [ANZ]                2     3       4       5     12

   Winter Olympics  Gold.1  Silver.1  Bronze.1  Total.1  # Games  Gold.2  \
0                0       0         0         0        0       13       0
1                3       0         0         0        0       15       5
2               18       0         0         0        0       41      18
3                6       0         0         0        0       11       1
4                0       0         0         0        0        2       3

   Silver.2  Bronze.2  Combined total
0         0         2               2
1         2         8              15
2        24        28              70
3         2         9              12
4         4         5              12
複製程式碼

Python 資料清理:回顧以及其他資源

在本教程中,你學習瞭如何使用 drop() 函式刪除不必要的資訊,以及如何給你的資料集設定索引以便更加方便的引用其他的項。

此外,你也學習瞭如何使用 .str() 清理物件欄位,以及如何使用 applymap() 函式清理整個資料集。最後,我們探索了一下如何跳過 CSV 檔案中某些列以及使用 rename() 方法重新命名列。

瞭解資料清理非常重要,因為這是資料科學很重要的一部分。你現在已經對如何使用 Pandas 以及 NumPy 清理資料集有了基本的瞭解。

檢視以下連結可以幫你找到更多的資源繼續你的 Python 資料科學之旅:

Real Python 的每一個教程都是由一組開發人員建立,所以它符合我們的高質量標準。參與本教程的團隊成員是 Malay Agarwal (作者)以及 Brad Solomon (編輯)。


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

相關文章