|
|
|
|
移动端

使用Pandas&NumPy进行数据清洗的6大常用方法

数据科学家花了大量的时间清洗数据集,并将这些数据转换为他们可以处理的格式。事实上,很多数据科学家声称开始获取和清洗数据的工作量要占整个工作的80%。

作者:Python数据科学来源:知乎|2018-04-03 12:07

【新品产上线啦】51CTO播客,随时随地,碎片化学习

数据科学家花了大量的时间清洗数据集,并将这些数据转换为他们可以处理的格式。事实上,很多数据科学家声称开始获取和清洗数据的工作量要占整个工作的80%。

因此,如果你正巧也在这个领域中,或者计划进入这个领域,那么处理这些杂乱不规则数据是非常重要的,这些杂乱数据包括一些缺失值,不连续格式,错误记录,或者是没有意义的异常值。

使用Pandas&NumPy进行数据清洗的6大常用方法

在这个教程中,我们将利用Python的 Pandas 和 Numpy 包来进行数据清洗。

主要内容如下:

  • 删除 DataFrame 中的不必要 columns
  • 改变 DataFrame 的 index
  • 使用 .str() 方法来清洗 columns
  • 使用 DataFrame.applymap() 函数按元素的清洗整个数据集
  • 重命名 columns 为一组更易识别的标签
  • 滤除 CSV文件中不必要的 rows

下面是要用到的数据集:

  • BL-Flickr-Images-Book.csv - 一份来自英国图书馆包含关于书籍信息的CSV文档
  • university_towns.txt - 一份包含美国各大洲大学城名称的text文档
  • olympics.csv - 一份总结了各国家参加夏季与冬季奥林匹克运动会情况的CSV文档

你可以从 Real Python 的 GitHub repository 下载数据集来进行下面的例子。

注意:建议使用Jupter Notebooks来学习下面的知识。

学习之前假设你已经有了对Pandas和Numpy库的基本认识,包括Pandas的工作基础 Series 和 DataFrame 对象,应用到这些对象上的常用方法,以及熟悉了NumPy的 NaN 值。

让我们导入这些模块开始我们的学习。

  1. >>> import pandas as pd 
  2. >>> import numpy as np 

删除DataFrame的列

经常的,你会发现数据集中不是所有的字段类型都是有用的。例如,你可能有一个关于学生信息的数据集,包含姓名,分数,标准,父母姓名,住址等具体信息,但是你只想分析学生的分数。

这个情况下,住址或者父母姓名信息对你来说就不是很重要。这些没有用的信息会占用不必要的空间,并会使运行时间减慢。

Pandas提供了一个非常便捷的方法 drop() 函数来移除一个DataFrame中不想要的行或列。让我们看一个简单的例子如何从DataFrame中移除列。

首先,我们引入 BL-Flickr-Images-Book.csv 文件,并创建一个此文件的DataFrame。在下面这个例子中,我们设置了一个 pd.read_csv 的相对路径,意味着所有的数据集都在 Datasets 文件夹下的当前工作目录中:

  1. >>> df = pd.read_csv('Datasets/BL-Flickr-Images-Book.csv'
  2. >>> df.head() 
  3.  
  4.     Identifier             Edition Statement      Place of Publication  \ 
  5. 0         206                           NaN                    London 
  6. 1         216                           NaN  London; Virtue & Yorston 
  7. 2         218                           NaN                    London 
  8. 3         472                           NaN                    London 
  9. 4         480  A new edition, revised, etc.                    London 
  10.  
  11.   Date of Publication              Publisher  \ 
  12. 0         1879 [1878]       S. Tinsley & Co. 
  13. 1                1868           Virtue & Co. 
  14. 2                1869  Bradbury, Evans & Co. 
  15. 3                1851          James Darling 
  16. 4                1857   Wertheim & Macintosh 
  17.  
  18.                                                Title     Author  \ 
  19. 0                  Walter Forbes. [A novel.] By A. A      A. A. 
  20. 1  All for Greed. [A novel. The dedication signed...  A., A. A. 
  21. 2  Love the Avenger. By the author of “All for Gr...  A., A. A. 
  22. 3  Welsh Sketches, chiefly ecclesiastical, to the...  A., E. S. 
  23. 4  [The World in which I live, and my place in it...  A., E. S. 
  24.  
  25.                                    Contributors  Corporate Author  \ 
  26. 0                               FORBES, Walter.               NaN 
  27. 1  BLAZE DE BURY, Marie Pauline Rose - Baroness               NaN 
  28. 2  BLAZE DE BURY, Marie Pauline Rose - Baroness               NaN 
  29. 3                   Appleyard, Ernest Silvanus.               NaN 
  30. 4                           BROOME, John Henry.               NaN 
  31.  
  32.    Corporate Contributors Former owner  Engraver Issuance type  \ 
  33. 0                     NaN          NaN       NaN   monographic 
  34. 1                     NaN          NaN       NaN   monographic 
  35. 2                     NaN          NaN       NaN   monographic 
  36. 3                     NaN          NaN       NaN   monographic 
  37. 4                     NaN          NaN       NaN   monographic 
  38.  
  39.                                           Flickr URL  \ 
  40. 0  http://www.flickr.com/photos/britishlibrary/ta... 
  41. 1  http://www.flickr.com/photos/britishlibrary/ta... 
  42. 2  http://www.flickr.com/photos/britishlibrary/ta... 
  43. 3  http://www.flickr.com/photos/britishlibrary/ta... 
  44. 4  http://www.flickr.com/photos/britishlibrary/ta... 
  45.  
  46.                             Shelfmarks 
  47. 0    British Library HMNTS 12641.b.30. 
  48. 1    British Library HMNTS 12626.cc.2. 
  49. 2    British Library HMNTS 12625.dd.1. 
  50. 3    British Library HMNTS 10369.bbb.15. 
  51. 4    British Library HMNTS 9007.d.28. 

我们使用了 head() 方法得到了前五个行信息,这些列提供了对图书馆有帮助的辅助信息,但是并不能很好的描述这些书籍: Edition Statement , Corporate Author , Corporate Contributors , Former owner , Engraver , Issuance type and Shelfmarks 。

因此,我们可以用下面的方法移除这些列:

  1. >>> to_drop = ['Edition Statement'
  2. ...            'Corporate Author'
  3. ...            'Corporate Contributors'
  4. ...            'Former owner'
  5. ...            'Engraver'
  6. ...            'Contributors'
  7. ...            'Issuance type'
  8. ...            'Shelfmarks'
  9.  
  10. >>> df.drop(to_drop, inplace=True, axis=1) 

在上面,我们定义了一个包含我们不要的列的名称列表。接着,我们在对象上调用 drop() 函数,其中 inplace 参数是 True , axis 参数是 1 。这告诉了Pandas我们想要直接在我们的对象上发生改变,并且它应该可以寻找对象中被移除列的信息。

我们再次看一下DataFrame,我们会看到不要想的信息已经被移除了。

  1. >>> df.head() 
  2.    Identifier      Place of Publication Date of Publication  \ 
  3. 0         206                    London         1879 [1878] 
  4. 1         216  London; Virtue & Yorston                1868 
  5. 2         218                    London                1869 
  6. 3         472                    London                1851 
  7. 4         480                    London                1857 
  8.  
  9.                Publisher                                              Title  \ 
  10. 0       S. Tinsley & Co.                  Walter Forbes. [A novel.] By A. A 
  11. 1           Virtue & Co.  All for Greed. [A novel. The dedication signed... 
  12. 2  Bradbury, Evans & Co.  Love the Avenger. By the author of “All for Gr... 
  13. 3          James Darling  Welsh Sketches, chiefly ecclesiastical, to the... 
  14. 4   Wertheim & Macintosh  [The World in which I live, and my place in it... 
  15.  
  16.       Author                                         Flickr URL 
  17. 0      A. A.  http://www.flickr.com/photos/britishlibrary/ta... 
  18. 1  A., A. A.  http://www.flickr.com/photos/britishlibrary/ta... 
  19. 2  A., A. A.  http://www.flickr.com/photos/britishlibrary/ta... 
  20. 3  A., E. S.  http://www.flickr.com/photos/britishlibrary/ta... 
  21. 4  A., E. S.  http://www.flickr.com/photos/britishlibrary/ta... 

同样的,我们也可以通过给 columns 参数赋值直接移除列,而就不用分别定义to_drop列表和axis了。

  1. >>> df.drop(columns=to_drop, inplace=True

这种语法更直观更可读。我们这里将要做什么就很明显了。

改变DataFrame的索引

Pandas索引 index 扩展了Numpy数组的功能,以允许更多多样化的切分和标记。在很多情况下,使用唯一的值作为索引值识别数据字段是非常有帮助的。

例如,仍然使用上一节的数据集,可以想象当一个图书管理员寻找一个记录,他们也许会输入一个唯一标识来定位一本书。

  1. >>> df['Identifier'].is_unique 
  2. True 

让我们用 set_index 把已经存在的索引改为这个列。

  1. >>> df = df.set_index('Identifier'
  2. >>> df.head() 
  3.                 Place of Publication Date of Publication  \ 
  4. 206                           London         1879 [1878] 
  5. 216         London; Virtue & Yorston                1868 
  6. 218                           London                1869 
  7. 472                           London                1851 
  8. 480                           London                1857 
  9.  
  10.                         Publisher  \ 
  11. 206              S. Tinsley & Co. 
  12. 216                  Virtue & Co. 
  13. 218         Bradbury, Evans & Co. 
  14. 472                 James Darling 
  15. 480          Wertheim & Macintosh 
  16.  
  17.                                                         Title     Author  \ 
  18. 206                         Walter Forbes. [A novel.] By A. A      A. A. 
  19. 216         All for Greed. [A novel. The dedication signed...  A., A. A. 
  20. 218         Love the Avenger. By the author of “All for Gr...  A., A. A. 
  21. 472         Welsh Sketches, chiefly ecclesiastical, to the...  A., E. S. 
  22. 480         [The World in which I live, and my place in it...  A., E. S. 
  23.  
  24.                                                    Flickr URL 
  25. 206         http://www.flickr.com/photos/britishlibrary/ta... 
  26. 216         http://www.flickr.com/photos/britishlibrary/ta... 
  27. 218         http://www.flickr.com/photos/britishlibrary/ta... 
  28. 472         http://www.flickr.com/photos/britishlibrary/ta... 
  29. 480         http://www.flickr.com/photos/britishlibrary/ta... 

技术细节:不像在SQL中的主键一样,pandas的索引不保证唯一性,尽管许多索引和合并操作将会使运行时间变长如果是这样。

我们可以用一个直接的方法 loc[] 来获取每一条记录。尽管 loc[] 这个词可能看上去没有那么直观,但它允许我们使用 基于标签 的索引,这个索引是行的标签或者不考虑位置的记录。

  1. >>> df.loc[206] 
  2. Place of Publication                                               London 
  3. Date of Publication                                           1879 [1878] 
  4. Publisher                                                S. Tinsley & Co. 
  5. Title                                   Walter Forbes. [A novel.] By A. A 
  6. Author                                                              A. A. 
  7. Flickr URL              http://www.flickr.com/photos/britishlibrary/ta... 
  8. Name: 206, dtype: object 

换句话说,206是索引的第一个标签。如果想通过位置获取它,我们可以使用 df.iloc[0] ,是一个 基于位置 的索引。

之前,我们的索引是一个范围索引:从0开始的整数,类似Python的内建 range 。通过给 set_index 一个列名,我们就把索引变成了 Identifier 中的值。

你也许注意到了我们通过 df = df.set_index(...) 的返回变量重新给对象赋了值。这是因为,默认的情况下,这个方法返回一个被改变对象的拷贝,并且它不会直接对原对象做任何改变。我们可以通过设置参数 inplace 来避免这个问题。

  1. df.set_index('Identifier', inplace=True

清洗数据字段

到现在为止,我们移除了不必要的列并改变了我们的索引变得更有意义。这个部分,我们将清洗特殊的列,并使它们变成统一的格式,这样可以更好的理解数据集和加强连续性。特别的,我们将清洗 Date of Publication 和 Place of Publication 。

根据上面观察,所有的数据类型都是现在的 object dtype类型,差不多类似于Python中的str。

它包含了一些不能被适用于数值或是分类的数据。这也正常,因为我们正在处理这些初始值就是杂乱无章字符串的数据。

  1. >>> df.get_dtype_counts() 
  2. object    6 

一个需要被改变为数值的的字段是 the date of publication 所以我们做如下操作:

  1. >>> df.loc[1905:, 'Date of Publication'].head(10) 
  2. Identifier 
  3. 1905           1888 
  4. 1929    1839, 38-54 
  5. 2836        [1897?] 
  6. 2854           1865 
  7. 2956        1860-63 
  8. 2957           1873 
  9. 3017           1866 
  10. 3131           1899 
  11. 4598           1814 
  12. 4884           1820 
  13. NameDate of Publication, dtype: object 

一本书只能有一个出版日期 data of publication 。因此,我们需要做以下的一些事情:

  • 移除在方括号内的额外日期,任何存在的:1879[1878]。
  • 将日期范围转化为它们的起始日期,任何存在的:1860-63;1839,38-54。
  • 完全移除我们不关心的日期,并用Numpy的 NaN 替换:[1879?]。
  • 将字符串 nan 转化为Numpy的 NaN 值。

考虑这些模式,我们可以用一个简单的正则表达式来提取出版日期:

  1. regex = r'^(\d{4})' 

上面正则表达式的意思在字符串开头寻找任何四位数字,符合我们的情况。

\d 代表任何数字, {4} 重复这个规则四次。 ^ 符号匹配一个字符串最开始的部分,圆括号表示一个分组,提示pandas我们想要提取正则表达式的部分。

让我们看看运行这个正则在数据集上之后会发生什么。

  1. >>> extr = df['Date of Publication'].str.extract(r'^(\d{4})', expand=False
  2. >>> extr.head() 
  3. Identifier 
  4. 206    1879 
  5. 216    1868 
  6. 218    1869 
  7. 472    1851 
  8. 480    1857 
  9. NameDate of Publication, dtype: object 

其实这个列仍然是一个 object 类型,但是我们可以使用 pd.to_numeric 轻松的得到数字的版本:

  1. >>> df['Date of Publication'] = pd.to_numeric(extr) 
  2. >>> df['Date of Publication'].dtype 
  3. dtype('float64'

这个结果中,10个值里大约有1个值缺失,这让我们付出了很小的代价来对剩余有效的值做计算。

  1. >>> df['Date of Publication'].isnull().sum() / len(df) 
  2. 0.11717147339205986 

结合str方法与Numpy清洗列

上面,你可以观察到 df['Date of Publication'].str. 的使用。这个属性是pandas里的一种提升字符串操作速度的方法,并有大量的Python字符串或编译的正则表达式上的小操作,例如 .split() , .replace() ,和 .capitalize() 。

为了清洗 Place of Publication 字段,我们可以结合pandas的 str 方法和numpy的 np.where 函数配合完成。

它的语法如下:

  1. >>> np.where(condition, thenelse

这里, condition 可以使一个类数组的对象,也可以是一个布尔表达。如果 condition 值为真,那么 then 将被使用,否则使用 else 。

它也可以组网使用,允许我们基于多个条件进行计算。

  1. >>> np.where(condition1, x1,  
  2.         np.where(condition2, x2,  
  3.             np.where(condition3, x3, ...))) 

我们将使用这两个方程来清洗 Place of Publication 由于这列有字符串对象。以下是这个列的内容:

  1. >>> df['Place of Publication'].head(10) 
  2. Identifier 
  3. 206                                  London 
  4. 216                London; Virtue & Yorston 
  5. 218                                  London 
  6. 472                                  London 
  7. 480                                  London 
  8. 481                                  London 
  9. 519                                  London 
  10. 667     pp. 40. G. Bryan & Co: Oxford, 1898 
  11. 874                                 London] 
  12. 1143                                 London 
  13. Name: Place of Publication, dtype: object 

我们看到,对于一些行, place of publication 还被一些其它没有用的信息围绕着。如果我们看更多的值,我们发现这种情况中有些行

让我们看看两个特殊的:

  1. >>> df.loc[4157862] 
  2. Place of Publication                                  Newcastle-upon-Tyne 
  3. Date of Publication                                                  1867 
  4. Publisher                                                      T. Fordyce 
  5. Title                   Local Records; or, Historical Register of rema... 
  6. Author                                                        T.  Fordyce 
  7. Flickr URL              http://www.flickr.com/photos/britishlibrary/ta... 
  8. Name: 4157862, dtype: object 
  9.  
  10. >>> df.loc[4159587] 
  11. Place of Publication                                  Newcastle upon Tyne 
  12. Date of Publication                                                  1834 
  13. Publisher                                                Mackenzie & Dent 
  14. Title                   An historical, topographical and descriptive v... 
  15. Author                                               E. (Eneas) Mackenzie 
  16. Flickr URL              http://www.flickr.com/photos/britishlibrary/ta... 
  17. Name: 4159587, dtype: object 

这两本书在同一个地方出版,但是一个有连字符,另一个没有。

为了一次性清洗这个列,我们使用 str.contains() 来获取一个布尔值。

我们清洗的列如下:

  1. >>> pub = df['Place of Publication'
  2. >>> london = pub.str.contains('London'
  3. >>> london[:5] 
  4. Identifier 
  5. 206    True 
  6. 216    True 
  7. 218    True 
  8. 472    True 
  9. 480    True 
  10. Name: Place of Publication, dtype: bool 
  11.  
  12. >>> oxford = pub.str.contains('Oxford'

我们将它与 np.where 结合。

  1. df['Place of Publication'] = np.where(london, 'London'
  2.                                       np.where(oxford, 'Oxford'
  3.                                                pub.str.replace('-'' '))) 
  4.  
  5. >>> df['Place of Publication'].head() 
  6. Identifier 
  7. 206    London 
  8. 216    London 
  9. 218    London 
  10. 472    London 
  11. 480    London 
  12. Name: Place of Publication, dtype: object 

这里, np.where 方程在一个嵌套的结构中被调用, condition 是一个通过 st.contains() 得到的布尔的 Series 。 contains() 方法与Python内建的 in 关键字一样,用于发现一个个体是否发生在一个迭代器中。

使用的替代物是一个代表我们期望的出版社地址字符串。我们也使用 str.replace() 将连字符替换为空格,然后给DataFrame中的列重新赋值。

尽管数据集中还有更多的不干净数据,但是我们现在仅讨论这两列。

让我们看看前五行,现在看起来比我们刚开始的时候好点了。

  1. >>> df.head() 
  2.            Place of Publication Date of Publication              Publisher  \ 
  3. 206                      London                1879        S. Tinsley & Co. 
  4. 216                      London                1868           Virtue & Co. 
  5. 218                      London                1869  Bradbury, Evans & Co. 
  6. 472                      London                1851          James Darling 
  7. 480                      London                1857   Wertheim & Macintosh 
  8.  
  9.                                                         Title    Author  \ 
  10. 206                         Walter Forbes. [A novel.] By A. A        AA 
  11. 216         All for Greed. [A novel. The dedication signed...   A. A A. 
  12. 218         Love the Avenger. By the author of “All for Gr...   A. A A. 
  13. 472         Welsh Sketches, chiefly ecclesiastical, to the...   E. S A. 
  14. 480         [The World in which I live, and my place in it...   E. S A. 
  15.  
  16.                                                    Flickr URL 
  17. 206         http://www.flickr.com/photos/britishlibrary/ta... 
  18. 216         http://www.flickr.com/photos/britishlibrary/ta... 
  19. 218         http://www.flickr.com/photos/britishlibrary/ta... 
  20. 472         http://www.flickr.com/photos/britishlibrary/ta... 
  21. 480         http://www.flickr.com/photos/britishlibrary/ta... 

在这一点上, Place of Publication 就是一个很好的需要被转换成分类数据的类型,因为我们可以用整数将这相当小的唯一城市集编码。(分类数据的使用内存与分类的数量以及数据的长度成正比)

使用 applymap 方法清洗整个数据集

在一定的情况下,你将看到并不是仅仅有一条列不干净,而是更多的。

在一些实例中,使用一个定制的函数到DataFrame的每一个元素将会是很有帮助的。 pandas 的 applyma() 方法与内建的 map() 函数相似,并且简单的应用到一个 DataFrame 中的所有元素上。

让我们看一个例子。我们将基于"university_towns.txt"文件创建一个 DataFrame 。

  1. $ head Datasets/univerisity_towns.txt 
  2. Alabama[edit] 
  3. Auburn (Auburn University)[1] 
  4. Florence (University of North Alabama) 
  5. Jacksonville (Jacksonville State University)[2] 
  6. Livingston (University of West Alabama)[2] 
  7. Montevallo (University of Montevallo)[2] 
  8. Troy (Troy University)[2] 
  9. Tuscaloosa (University of Alabama, Stillman College, Shelton State)[3][4] 
  10. Tuskegee (Tuskegee University)[5] 
  11. Alaska[edit] 

我们可以看到每个state后边都有一些在那个state的大学城: StateA TownA1 TownA2 StateB TownB1 TownB2... 。如果我们仔细观察state名字的写法,我们会发现它们都有"[edit]"的自字符串。

我们可以利用这个特征创建一个含有 (state,city) 元组的列表,并将这个列表嵌入到 DdataFrame 中。

  1. >>> university_towns = [] 
  2. >>> with open('Datasets/university_towns.txt'as file: 
  3. ...     for line in file: 
  4. ...         if '[edit]' in line: 
  5. ...             # Remember this `state` until the next is found 
  6. ...             state = line 
  7. ...         else
  8. ...             # Otherwise, we have a city; keep `state` as last-seen 
  9. ...             university_towns.append((state, line)) 
  10.  
  11. >>> university_towns[:5] 
  12. [('Alabama[edit]\n''Auburn (Auburn University)[1]\n'), 
  13.  ('Alabama[edit]\n''Florence (University of North Alabama)\n'), 
  14.  ('Alabama[edit]\n''Jacksonville (Jacksonville State University)[2]\n'), 
  15.  ('Alabama[edit]\n''Livingston (University of West Alabama)[2]\n'), 
  16.  ('Alabama[edit]\n''Montevallo (University of Montevallo)[2]\n')] 

我们可以在DataFrame中包装这个列表,并设列名为"State"和"RegionName"。pandas将会使用列表中的每个元素,然后设置 State 到左边的列, RegionName 到右边的列。

最终的DataFrame是这样的:

  1. >>> towns_df = pd.DataFrame(university_towns, 
  2. ...                         columns=['State''RegionName']) 
  3.  
  4. >>> towns_df.head() 
  5.  State                                         RegionName 
  6. 0  Alabama[edit]\n                    Auburn (Auburn University)[1]\n 
  7. 1  Alabama[edit]\n           Florence (University of North Alabama)\n 
  8. 2  Alabama[edit]\n  Jacksonville (Jacksonville State University)[2]\n 
  9. 3  Alabama[edit]\n       Livingston (University of West Alabama)[2]\n 
  10. 4  Alabama[edit]\n         Montevallo (University of Montevallo)[2]\n 

我们可以像上面使用for loop来进行清洗,但是pandas提供了更简单的办法。我们只需要state name和town name,然后就可以移除所以其他的了。这里我们可以再次使用pandas的 .str() 方法,同时我们也可以使用 applymap() 将一个python callable映射到DataFrame中的每个元素上。

我们一直在使用"元素"这个摄于,但是我们到底是什么意思呢?看看下面这个"toy"的DataFrame:

  1. 0           1 
  2. 0    Mock     Dataset 
  3. 1  Python     Pandas 
  4. 2    Real     Python 
  5. 3   NumPy     Clean 

在这个例子中,每个单元 (‘Mock’, ‘Dataset’, ‘Python’, ‘Pandas’, etc.) 都是一个元素。因此, applymap() 将分别应用一个函数到这些元素上。让我们定义这个函数。

  1. >>> def get_citystate(item): 
  2. ...     if ' (' in item: 
  3. ...         return item[:item.find(' (')] 
  4. ...     elif '[' in item: 
  5. ...         return item[:item.find('[')] 
  6. ...     else
  7. ...         return item 

pandas的 applymap() 只用一个参数,就是要应用到每个元素上的函数(callable)。

  1. >>> towns_df =  towns_df.applymap(get_citystate) 

首先,我们定义一个函数,它将从DataFrame中获取每一个元素作为自己的参数。在这个函数中,检验元素中是否有一个 ( 或者 [ 。

基于上面的检查,函数返回相应的值。最后, applymap() 函数被用在我们的对象上。现在DataFrame就看起来更干静了。

  1. >>> towns_df.head() 
  2.      State    RegionName 
  3. 0  Alabama        Auburn 
  4. 1  Alabama      Florence 
  5. 2  Alabama  Jacksonville 
  6. 3  Alabama    Livingston 
  7. 4  Alabama    Montevallo 

applymap() 方法从DataFrame中提取每个元素,传递到函数中,然后覆盖原来的值。就是这么简单!

技术细节:虽然 .applymap 是一个方便和灵活的方法,但是对于大的数据集它将会花费很长时间运行,因为它需要将python callable应用到每个元素上。一些情况中,使用Cython或者NumPY的向量化的操作会更高效。

重命名列和移除行

经常的,你处理的数据集会有让你不太容易理解的列名,或者在头几行或最后几行有一些不重要的信息,例如术语定义,或是附注。

这种情况下,我们想重新命名列和移除一定的行以让我们只留下正确和有意义的信息。

为了证明我们如何处理它,我们先看一下"olympics.csv"数据集的头5行:

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

现在我们将它读入pandas的DataFrame。

  1. >>> olympics_df = pd.read_csv('Datasets/olympics.csv'
  2. >>> olympics_df.head() 
  3.                    0         1     2     3     4      5         6     7     8  \ 
  4. 0                NaN  ? Summer  01 !  02 !  03 !  Total  ? Winter  01 !  02 ! 
  5. 1  Afghanistan (AFG)        13     0     0     2      2         0     0     0 
  6. 2      Algeria (ALG)        12     5     2     8     15         3     0     0 
  7. 3    Argentina (ARG)        23    18    24    28     70        18     0     0 
  8. 4      Armenia (ARM)         5     1     2     9     12         6     0     0 
  9.  
  10.       9     10       11    12    13    14              15 
  11. 0  03 !  Total  ? Games  01 !  02 !  03 !  Combined total 
  12. 1     0      0       13     0     0     2               2 
  13. 2     0      0       15     5     2     8              15 
  14. 3     0      0       41    18    24    28              70 
  15. 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"之类的。

因此,我们需要做两件事:

  • 移除第一行并设置header为第一行
  • 重新命名列

当我们读CSV文件的时候,可以通过传递一些参数到 read_csv 函数来移除行和设置列名称。

这个函数有很多可选桉树,但是这里我们只需要 header

来移除第0行:

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

我们现在有了设置为header的正确行,并且所有没用的行都被移除了。记录一下pandas是如何将包含国家的列名 NaN 改变为 Unnamed:0 的。

为了重命名列,我们将使用DataFrame的 rename() 方法,允许你以一个映射(这里是一个字典)重新标记一个轴。

让我们开始定义一个字典来将现在的列名称(键)映射到更多的可用列名称(字典的值)。

  1. >>> new_names =  {'Unnamed: 0''Country'
  2. ...               '? Summer''Summer Olympics'
  3. ...               '01 !''Gold'
  4. ...               '02 !''Silver'
  5. ...               '03 !''Bronze'
  6. ...               '? Winter''Winter Olympics'
  7. ...               '01 !.1''Gold.1'
  8. ...               '02 !.1''Silver.1'
  9. ...               '03 !.1''Bronze.1'
  10. ...               '? Games''# Games'
  11. ...               '01 !.2''Gold.2'
  12. ...               '02 !.2''Silver.2'
  13. ...               '03 !.2''Bronze.2'

我们在对象上调用 rename() 函数:

  1. >>> olympics_df.rename(columns=new_names, inplace=True

设置 inplace 为 True 可以让我们的改变直接反映在对象上。让我们看看是否正确:

  1. >>> olympics_df.head() 
  2.                    Country  Summer Olympics  Gold  Silver  Bronze  Total  \ 
  3. 0        Afghanistan (AFG)               13     0       0       2      2 
  4. 1            Algeria (ALG)               12     5       2       8     15 
  5. 2          Argentina (ARG)               23    18      24      28     70 
  6. 3            Armenia (ARM)                5     1       2       9     12 
  7. 4  Australasia (ANZ) [ANZ]                2     3       4       5     12 
  8.  
  9.    Winter Olympics  Gold.1  Silver.1  Bronze.1  Total.1  # Games  Gold.2  \ 
  10. 0                0       0         0         0        0       13       0 
  11. 1                3       0         0         0        0       15       5 
  12. 2               18       0         0         0        0       41      18 
  13. 3                6       0         0         0        0       11       1 
  14. 4                0       0         0         0        0        2       3 
  15.  
  16.    Silver.2  Bronze.2  Combined total 
  17. 0         0         2               2 
  18. 1         2         8              15 
  19. 2        24        28              70 
  20. 3         2         9              12 
  21. 4         4         5              12 

Python数据清洗:回顾

这个教程中,你学会了从数据集中如何使用 drop() 函数去除不必要的信息,也学会了如何为数据集设置索引,以让items可以被容易的找到。

更多的,你学会了如何使用 .str() 清洗对象字段,以及如何使用 applymap 对整个数据集清洗。最后,我们探索了如何移除CSV文件的行,并且使用 rename() 方法重命名列。

掌握数据清洗非常重要,因为它是数据科学的一个大的部分。你现在应该有了一个如何使用pandas和numpy进行数据清洗的基本理解了。

【编辑推荐】

  1. 克服大数据集群的挑战
  2. 数据集市的挑战:如何才能识别和处理数据安全
  3. 大数据和Hadoop时代的维度建模和Kimball数据集市
  4. 收集数据太困难?这里为你准备了71个免费数据集
  5. 遇到有这六大缺陷的数据集该怎么办?这有一份数据处理急救包
【责任编辑:未丽燕 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

JSP应用开发详解(第二版)

本书结合JSP和Servlet的最新规范,从基本的语法和规范入手,以经验为后盾,以实用为目标,以实例为导向,以实践为指导,深入浅出地讲解了JS...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊