合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
# 使用Pandas&NumPy进行数据清洗 原文链接:https://realpython.com/python-data-cleaning-numpy-pandas/ 数据科学家花了大量的时间清洗数据集,并将这些数据转换为他们可以处理的格式。事实上,很多数据科学家声称开始获取和清洗数据的工作量要占整个工作的80%。 因此,如果你正巧也在这个领域中,或者计划进入这个领域,那么处理这些杂乱不规则数据是非常重要的,这些杂乱数据包括一些缺失值,不连续格式,错误记录,或者是没有意义的异常值。 在这个教程中,我们将利用Python的`Pandas`和`Numpy`包来进行数据清洗。 主要内容如下: * 删除`DataFrame`中的不必要 columns * 改变`DataFrame`的 index * 使用`.str()`方法来清洗 columns * 使用`DataFrame.applymap()`函数按元素清洗整个数据集 * 重命名 columns 为一组更易识别的标签 * 滤除 CSV文件中不必要的 rows 下面是要用到的数据集: * [BL-Flickr-Images-Book.csv](https://github.com/realpython/python-data-cleaning/blob/master/Datasets/BL-Flickr-Images-Book.csv)– 一份来自英国图书馆包含关于书籍信息的CSV文档 * [university\_towns.txt](https://github.com/realpython/python-data-cleaning/blob/master/Datasets/university_towns.txt)– 一份包含美国各大洲大学城名称的text文档 * [olympics.csv](https://github.com/realpython/python-data-cleaning/blob/master/Datasets/olympics.csv)– 一份总结了各国家参加夏季与冬季奥林匹克运动会情况的CSV文档 你可以从`Real Python`的 GitHub repository 下载数据集来进行下面的例子。 > 注意:建议使用Jupter Notebooks来学习下面的知识。 学习之前假设你已经有了对Pandas和Numpy库的基本认识,包括Pandas的工作基础`Series`和`DataFrame`对象,应用到这些对象上的常用方法,以及熟悉了NumPy的`NaN`值。 让我们导入这些模块开始我们的学习。 ~~~text 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`文件夹下的当前工作目录中: ~~~text 1>>> df = pd.read_csv('Datasets/BL-Flickr-Images-Book.csv') 2>>> df.head() 3 Identifier Edition Statement Place of Publication \ 40 206 NaN London 51 216 NaN London; Virtue & Yorston 62 218 NaN London 73 472 NaN London 84 480 A new edition, revised, etc. London 9 Date of Publication Publisher \ 100 1879 [1878] S. Tinsley & Co. 111 1868 Virtue & Co. 122 1869 Bradbury, Evans & Co. 133 1851 James Darling 144 1857 Wertheim & Macintosh 15 Title Author \ 160 Walter Forbes. [A novel.] By A. A A. A. 171 All for Greed. [A novel. The dedication signed... A., A. A. 182 Love the Avenger. By the author of “All for Gr... A., A. A. 193 Welsh Sketches, chiefly ecclesiastical, to the... A., E. S. 204 [The World in which I live, and my place in it... A., E. S. 21 Contributors Corporate Author \ 220 FORBES, Walter. NaN 231 BLAZE DE BURY, Marie Pauline Rose - Baroness NaN 242 BLAZE DE BURY, Marie Pauline Rose - Baroness NaN 253 Appleyard, Ernest Silvanus. NaN 264 BROOME, John Henry. NaN 27 Corporate Contributors Former owner Engraver Issuance type \ 280 NaN NaN NaN monographic 291 NaN NaN NaN monographic 302 NaN NaN NaN monographic 313 NaN NaN NaN monographic 324 NaN NaN NaN monographic 33 Flickr URL \ 340 http://www.flickr.com/photos/britishlibrary/ta... 351 http://www.flickr.com/photos/britishlibrary/ta... 362 http://www.flickr.com/photos/britishlibrary/ta... 373 http://www.flickr.com/photos/britishlibrary/ta... 384 http://www.flickr.com/photos/britishlibrary/ta... 39 Shelfmarks 400 British Library HMNTS 12641.b.30. 411 British Library HMNTS 12626.cc.2. 422 British Library HMNTS 12625.dd.1. 433 British Library HMNTS 10369.bbb.15. 444 British Library HMNTS 9007.d.28. ~~~ 我们使用了`head()`方法得到了前五个行信息,这些列提供了对图书馆有帮助的辅助信息,但是并不能很好的描述这些书籍:`Edition Statement`,`Corporate Author`,`Corporate Contributors`,`Former owner`,`Engraver`,`Issuance type and Shelfmarks`。 因此,我们可以用下面的方法移除这些列: ~~~text 1>>> to_drop = ['Edition Statement', 2... 'Corporate Author', 3... 'Corporate Contributors', 4... 'Former owner', 5... 'Engraver', 6... 'Contributors', 7... 'Issuance type', 8... 'Shelfmarks'] 9>>> df.drop(to_drop, inplace=True, axis=1) ~~~ 在上面,我们定义了一个包含我们不要的列的名称列表。接着,我们在对象上调用`drop()`函数,其中`inplace`参数是`True`,`axis`参数是`1`。这告诉了Pandas,我们想要直接在我们的对象上发生改变,并且它应该可以寻找对象中被移除列的信息。 我们再次看一下DataFrame,我们会看到不想要的信息已经被移除了。 ~~~text 1>>> df.head() 2 Identifier Place of Publication Date of Publication \ 30 206 London 1879 [1878] 41 216 London; Virtue & Yorston 1868 52 218 London 1869 63 472 London 1851 74 480 London 1857 8 Publisher Title \ 90 S. Tinsley & Co. Walter Forbes. [A novel.] By A. A 101 Virtue & Co. All for Greed. [A novel. The dedication signed... 112 Bradbury, Evans & Co. Love the Avenger. By the author of “All for Gr... 123 James Darling Welsh Sketches, chiefly ecclesiastical, to the... 134 Wertheim & Macintosh [The World in which I live, and my place in it... 14 Author Flickr URL 150 A. A. http://www.flickr.com/photos/britishlibrary/ta... 161 A., A. A. http://www.flickr.com/photos/britishlibrary/ta... 172 A., A. A. http://www.flickr.com/photos/britishlibrary/ta... 183 A., E. S. http://www.flickr.com/photos/britishlibrary/ta... 194 A., E. S. http://www.flickr.com/photos/britishlibrary/ta... ~~~ 同样的,我们也可以通过给`columns`参数赋值直接移除列,而就不用分别定义to\_drop列表和axis了。 ~~~text 1>>> df.drop(columns=to_drop, inplace=True) ~~~ 这种语法更直观更可读。我们这里将要做什么就很明显了。 ## **改变DataFrame的索引** Pandas索引`index`扩展了Numpy数组的功能,以允许更多多样化的切分和标记。在很多情况下,使用唯一的值作为索引值识别数据字段是非常有帮助的。 例如,仍然使用上一节的数据集,可以想象当一个图书管理员寻找一个记录,他们也许会输入一个唯一标识来定位一本书。 ~~~text 1>>> df['Identifier'].is_unique 2True ~~~ 让我们用`set_index`把已经存在的索引改为这个列。 ~~~text 1>>> df = df.set_index('Identifier') 2>>> df.head() 3 Place of Publication Date of Publication \ 4206 London 1879 [1878] 5216 London; Virtue & Yorston 1868 6218 London 1869 7472 London 1851 8480 London 1857 9 Publisher \ 10206 S. Tinsley & Co. 11216 Virtue & Co. 12218 Bradbury, Evans & Co. 13472 James Darling 14480 Wertheim & Macintosh 15 Title Author \ 16206 Walter Forbes. [A novel.] By A. A A. A. 17216 All for Greed. [A novel. The dedication signed... A., A. A. 18218 Love the Avenger. By the author of “All for Gr... A., A. A. 19472 Welsh Sketches, chiefly ecclesiastical, to the... A., E. S. 20480 [The World in which I live, and my place in it... A., E. S. 21 Flickr URL 22206 http://www.flickr.com/photos/britishlibrary/ta... 23216 http://www.flickr.com/photos/britishlibrary/ta... 24218 http://www.flickr.com/photos/britishlibrary/ta... 25472 http://www.flickr.com/photos/britishlibrary/ta... 26480 http://www.flickr.com/photos/britishlibrary/ta... ~~~ > 技术细节:不像在SQL中的主键一样,pandas的索引不保证唯一性,尽管许多索引和合并操作将会使运行时间变长如果是这样。 我们可以用一个直接的方法`loc[]`来获取每一条记录。尽管`loc[]`这个词可能看上去没有那么直观,但它允许我们使用**基于标签**的索引,这个索引是行的标签或者不考虑位置的记录。 ~~~text 1>>> df.loc[206] 2Place of Publication London 3Date of Publication 1879 [1878] 4Publisher S. Tinsley & Co. 5Title Walter Forbes. [A novel.] By A. A 6Author A. A. 7Flickr URL http://www.flickr.com/photos/britishlibrary/ta... 8Name: 206, dtype: object ~~~ 换句话说,206是索引的第一个标签。如果想通过位置获取它,我们可以使用`df.iloc[0]`,是一个**基于位置**的索引。 之前,我们的索引是一个范围索引:从0开始的整数,类似Python的内建`range`。通过给`set_index`一个列名,我们就把索引变成了`Identifier`中的值。 你也许注意到了我们通过`df = df.set_index(...)`的返回变量重新给对象赋了值。这是因为,默认的情况下,这个方法返回一个被改变对象的拷贝,并且它不会直接对原对象做任何改变。我们可以通过设置参数`inplace`来避免这个问题。 ~~~text 1df.set_index('Identifier', inplace=True) ~~~ ## **清洗数据字段** 到现在为止,我们移除了不必要的列并改变了我们的索引,让它们变得更有意义。这个部分,我们将清洗特殊的列,并使它们变成统一的格式,这样可以更好的理解数据集和加强连续性。特别的,我们将清洗`Date of Publication`和`Place of Publication`。 根据上面观察,所有的数据类型都是`object`dtype类型,差不多类似于Python中的str。 它包含了一些不能被适用于数值或是分类的数据。这也正常,因为我们正在处理这些初始值就是杂乱无章字符串的数据。 ~~~text 1>>> df.get_dtype_counts() 2object 6 ~~~ 一个需要被改变为数值的的字段是`date of publication`所以我们做如下操作: ~~~text 1>>> df.loc[1905:, 'Date of Publication'].head(10) 2Identifier 31905 1888 41929 1839, 38-54 52836 [1897?] 62854 1865 72956 1860-63 82957 1873 93017 1866 103131 1899 114598 1814 124884 1820 13Name: Date of Publication, dtype: object ~~~ 一本书只能有一个出版日期`data of publication`。因此,我们需要做以下的一些事情: * 移除在方括号内的额外日期,任何存在的:1879\[1878\]。 * 将日期范围转化为它们的起始日期,任何存在的:1860-63;1839,38-54。 * 完全移除我们不关心的日期,并用Numpy的`NaN`替换:\[1879?\]。 * 将字符串`nan`转化为Numpy的`NaN`值。 考虑这些模式,我们可以用一个简单的正则表达式来提取出版日期: ~~~text 1regex = r'^(\d{4})' ~~~ 上面正则表达式的意思在字符串开头寻找任何四位数字,符合我们的情况。 `\d`代表任何数字,`{4}`重复这个规则四次。`^`符号匹配一个字符串最开始的部分,圆括号表示一个分组,提示pandas我们想要提取正则表达式的部分。 让我们看看运行这个正则在数据集上之后会发生什么。 ~~~text 1>>> extr = df['Date of Publication'].str.extract(r'^(\d{4})', expand=False) 2>>> extr.head() 3Identifier 4206 1879 5216 1868 6218 1869 7472 1851 8480 1857 9Name: Date of Publication, dtype: object ~~~ 其实这个列仍然是一个`object`类型,但是我们可以使用`pd.to_numeric`轻松的得到数字的版本: ~~~text 1>>> df['Date of Publication'] = pd.to_numeric(extr) 2>>> df['Date of Publication'].dtype 3dtype('float64') ~~~ 这个结果中,10个值里大约有1个值缺失,这让我们付出了很小的代价来对剩余有效的值做计算。 ~~~text 1>>> df['Date of Publication'].isnull().sum() / len(df) 20.11717147339205986 ~~~ ## **结合str方法与Numpy清洗列** 上面,你可以观察到`df['Date of Publication'].str.`的使用。这个属性是pandas里的一种提升字符串操作速度的方法,并有大量的Python字符串或编译的正则表达式上的小操作,例如`.split()`,`.replace()`,和`.capitalize()`。 为了清洗`Place of Publication`字段,我们可以结合pandas的`str`方法和numpy的`np.where`函数配合完成。 它的语法如下: ~~~text 1>>> np.where(condition, then, else) ~~~ 这里,`condition`可以使一个类数组的对象,也可以是一个布尔表达。如果`condition`值为真,那么`then`将被使用,否则使用`else`。 它也可以嵌套使用,允许我们基于多个条件进行计算。 ~~~text 1>>> np.where(condition1, x1, 2 np.where(condition2, x2, 3 np.where(condition3, x3, ...))) ~~~ 我们将使用这两个函数来清洗`Place of Publication`由于这列有字符串对象。以下是这个列的内容: ~~~text 1>>> df['Place of Publication'].head(10) 2Identifier 3206 London 4216 London; Virtue & Yorston 5218 London 6472 London 7480 London 8481 London 9519 London 10667 pp. 40. G. Bryan & Co: Oxford, 1898 11874 London] 121143 London 13Name: Place of Publication, dtype: object ~~~ 我们看到,对于一些行,`place of publication`还被一些其它没有用的信息围绕着。如果我们看更多的值,我们发现这种情况只适用于`place of publication`是"London"或者"Oxford"的行。 让我们看看两个特殊的: ~~~text 1>>> df.loc[4157862] 2Place of Publication Newcastle-upon-Tyne 3Date of Publication 1867 4Publisher T. Fordyce 5Title Local Records; or, Historical Register of rema... 6Author T. Fordyce 7Flickr URL http://www.flickr.com/photos/britishlibrary/ta... 8Name: 4157862, dtype: object 9>>> df.loc[4159587] 10Place of Publication Newcastle upon Tyne 11Date of Publication 1834 12Publisher Mackenzie & Dent 13Title An historical, topographical and descriptive v... 14Author E. (Eneas) Mackenzie 15Flickr URL http://www.flickr.com/photos/britishlibrary/ta... 16Name: 4159587, dtype: object ~~~ 这两本书在同一个地方出版,但是一个有连字符,另一个没有。 为了一次性清洗这个列,我们使用`str.contains()`来获取一个布尔值。 我们清洗的列如下: ~~~text 1>>> pub = df['Place of Publication'] 2>>> london = pub.str.contains('London') 3>>> london[:5] 4Identifier 5206 True 6216 True 7218 True 8472 True 9480 True 10Name: Place of Publication, dtype: bool 11>>> oxford = pub.str.contains('Oxford') ~~~ 我们将它与`np.where`结合。 ~~~text 1df['Place of Publication'] = np.where(london, 'London', 2 np.where(oxford, 'Oxford', 3 pub.str.replace('-', ' '))) 4>>> df['Place of Publication'].head() 5Identifier 6206 London 7216 London 8218 London 9472 London 10480 London 11Name: Place of Publication, dtype: object ~~~ 这里,`np.where`函数在一个嵌套的结构中被调用,`condition`是一个通过`st.contains()`得到的布尔的`Series`。`contains()`方法与Python内建的`in`关键字一样,用于发现一个个体是否发生在一个迭代器中。 使用的替代物是一个代表我们期望的出版社地址字符串。我们也使用`str.replace()`将连字符替换为空格,然后给DataFrame中的列重新赋值。 尽管数据集中还有更多的不干净数据,但是我们现在仅讨论这两列。 让我们看看前五行,现在看起来比我们刚开始的时候好点了。 ~~~text 1>>> df.head() 2 Place of Publication Date of Publication Publisher \ 3206 London 1879 S. Tinsley & Co. 4216 London 1868 Virtue & Co. 5218 London 1869 Bradbury, Evans & Co. 6472 London 1851 James Darling 7480 London 1857 Wertheim & Macintosh 8 Title Author \ 9206 Walter Forbes. [A novel.] By A. A AA 10216 All for Greed. [A novel. The dedication signed... A. A A. 11218 Love the Avenger. By the author of “All for Gr... A. A A. 12472 Welsh Sketches, chiefly ecclesiastical, to the... E. S A. 13480 [The World in which I live, and my place in it... E. S A. 14 Flickr URL 15206 http://www.flickr.com/photos/britishlibrary/ta... 16216 http://www.flickr.com/photos/britishlibrary/ta... 17218 http://www.flickr.com/photos/britishlibrary/ta... 18472 http://www.flickr.com/photos/britishlibrary/ta... 19480 http://www.flickr.com/photos/britishlibrary/ta... ~~~ > 在这一点上,`Place of Publication`就是一个很好的需要被转换成分类数据的类型,因为我们可以用整数将这相当小的唯一城市集编码。(分类数据的使用内存与分类的数量以及数据的长度成正比) ## **使用`applymap`方法清洗整个数据集** 在一定的情况下,你将看到并不是仅仅有一条列不干净,而是更多的。 在一些实例中,使用一个定制的函数到DataFrame的每一个元素将会是很有帮助的。`pandas`的`applymap()`方法与内建的`map()`函数相似,并且简单的应用到一个`DataFrame`中的所有元素上。 让我们看一个例子。我们将基于"university\_towns.txt"文件创建一个`DataFrame`。 ~~~text 1$ head Datasets/univerisity_towns.txt 2Alabama[edit] 3Auburn (Auburn University)[1] 4Florence (University of North Alabama) 5Jacksonville (Jacksonville State University)[2] 6Livingston (University of West Alabama)[2] 7Montevallo (University of Montevallo)[2] 8Troy (Troy University)[2] 9Tuscaloosa (University of Alabama, Stillman College, Shelton State)[3][4] 10Tuskegee (Tuskegee University)[5] 11Alaska[edit] ~~~ 我们可以看到每个state后边都有一些在那个state的大学城:`StateA TownA1 TownA2 StateB TownB1 TownB2...`。如果我们仔细观察state名字的写法,我们会发现它们都有"\[edit\]"的自字符串。 我们可以利用这个特征创建一个含有`(state,city)`元组的列表,并将这个列表嵌入到`DdataFrame`中, ~~~text 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>>> university_towns[:5] 11[('Alabama[edit]\n', 'Auburn (Auburn University)[1]\n'), 12 ('Alabama[edit]\n', 'Florence (University of North Alabama)\n'), 13 ('Alabama[edit]\n', 'Jacksonville (Jacksonville State University)[2]\n'), 14 ('Alabama[edit]\n', 'Livingston (University of West Alabama)[2]\n'), 15 ('Alabama[edit]\n', 'Montevallo (University of Montevallo)[2]\n')] ~~~ 我们可以在DataFrame中包装这个列表,并设列名为"State"和"RegionName"。pandas将会使用列表中的每个元素,然后设置`State`到左边的列,`RegionName`到右边的列。 最终的DataFrame是这样的: ~~~text 1>>> towns_df = pd.DataFrame(university_towns, 2... columns=['State', 'RegionName']) 3>>> towns_df.head() 4 State RegionName 50 Alabama[edit]\n Auburn (Auburn University)[1]\n 61 Alabama[edit]\n Florence (University of North Alabama)\n 72 Alabama[edit]\n Jacksonville (Jacksonville State University)[2]\n 83 Alabama[edit]\n Livingston (University of West Alabama)[2]\n 94 Alabama[edit]\n Montevallo (University of Montevallo)[2]\n ~~~ 我们可以像上面使用for loop来进行清洗,但是pandas提供了更简单的办法。我们只需要state name和town name,然后就可以移除所以其他的了。这里我们可以再次使用pandas的`.str()`方法,同时我们也可以使用`applymap()`将一个python callable映射到DataFrame中的每个元素上。 我们一直在使用"元素"这个摄于,但是我们到底是什么意思呢?看看下面这个"toy"的DataFrame: ~~~text 1 0 1 20 Mock Dataset 31 Python Pandas 42 Real Python 53 NumPy Clean ~~~ 在这个例子中,每个单元 (‘Mock’, ‘Dataset’, ‘Python’, ‘Pandas’, etc.) 都是一个元素。因此,`applymap()`将分别应用一个函数到这些元素上。让我们定义这个函数。 ~~~text 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)。 ~~~text 1>>> towns_df = towns_df.applymap(get_citystate) ~~~ 首先,我们定义一个函数,它将从DataFrame中获取每一个元素作为自己的参数。在这个函数中,检验元素中是否有一个`(`或者`[`。 基于上面的检查,函数返回相应的值。最后,`applymap()`函数被用在我们的对象上。现在DataFrame就看起来更干净了。 ~~~text 1>>> towns_df.head() 2 State RegionName 30 Alabama Auburn 41 Alabama Florence 52 Alabama Jacksonville 63 Alabama Livingston 74 Alabama Montevallo ~~~ `applymap()`方法从DataFrame中提取每个元素,传递到函数中,然后覆盖原来的值。就是这么简单! > 技术细节:虽然`.applymap`是一个方便和灵活的方法,但是对于大的数据集它将会花费很长时间运行,因为它需要将python callable应用到每个元素上。一些情况中,使用Cython或者NumPY的矢量化的操作会更高效。 ## **重命名列和移除行** 经常的,你处理的数据集会有让你不太容易理解的列名,或者在头几行或最后几行有一些不重要的信息,例如术语定义,或是附注。 这种情况下,我们想重新命名列和移除一定的行以让我们只留下正确和有意义的信息。 为了证明我们如何处理它,我们先看一下"olympics.csv"数据集的头5行: ~~~text 1$ head -n 5 Datasets/olympics.csv 20,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 4Afghanistan (AFG),13,0,0,2,2,0,0,0,0,0,13,0,0,2,2 5Algeria (ALG),12,5,2,8,15,3,0,0,0,0,15,5,2,8,15 6Argentina (ARG),23,18,24,28,70,18,0,0,0,0,41,18,24,28,70 ~~~ 现在我们将它读入pandas的DataFrame。 ~~~text 1>>> olympics_df = pd.read_csv('Datasets/olympics.csv') 2>>> olympics_df.head() 3 0 1 2 3 4 5 6 7 8 \ 40 NaN ? Summer 01 ! 02 ! 03 ! Total ? Winter 01 ! 02 ! 51 Afghanistan (AFG) 13 0 0 2 2 0 0 0 62 Algeria (ALG) 12 5 2 8 15 3 0 0 73 Argentina (ARG) 23 18 24 28 70 18 0 0 84 Armenia (ARM) 5 1 2 9 12 6 0 0 9 9 10 11 12 13 14 15 100 03 ! Total ? Games 01 ! 02 ! 03 ! Combined total 111 0 0 13 0 0 2 2 122 0 0 15 5 2 8 15 133 0 0 41 18 24 28 70 144 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行: ~~~text 1>>> olympics_df = pd.read_csv('Datasets/olympics.csv', header=1) 2>>> olympics_df.head() 3 Unnamed: 0 ? Summer 01 ! 02 ! 03 ! Total ? Winter \ 40 Afghanistan (AFG) 13 0 0 2 2 0 51 Algeria (ALG) 12 5 2 8 15 3 62 Argentina (ARG) 23 18 24 28 70 18 73 Armenia (ARM) 5 1 2 9 12 6 84 Australasia (ANZ) [ANZ] 2 3 4 5 12 0 9 01 !.1 02 !.1 03 !.1 Total.1 ? Games 01 !.2 02 !.2 03 !.2 \ 100 0 0 0 0 13 0 0 2 111 0 0 0 0 15 5 2 8 122 0 0 0 0 41 18 24 28 133 0 0 0 0 11 1 2 9 144 0 0 0 0 2 3 4 5 15 Combined total 160 2 171 15 182 70 193 12 204 12 ~~~ 我们现在有了设置为header的正确行,并且所有没用的行都被移除了。记录一下pandas是如何将包含国家的列名`NaN`改变为`Unnamed:0`的。 为了重命名列,我们将使用DataFrame的`rename()`方法,允许你以一个映射(这里是一个字典)重新标记一个轴。 让我们开始定义一个字典将现有的列名称(键)映射到更多的可用列名称上(字典的值)。 ~~~text 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()`函数: ~~~text 1>>> olympics_df.rename(columns=new_names, inplace=True) ~~~ 设置`inplace`为`True`可以让我们的改变直接反映在对象上。让我们看看是否正确: ~~~text 1>>> olympics_df.head() 2 Country Summer Olympics Gold Silver Bronze Total \ 30 Afghanistan (AFG) 13 0 0 2 2 41 Algeria (ALG) 12 5 2 8 15 52 Argentina (ARG) 23 18 24 28 70 63 Armenia (ARM) 5 1 2 9 12 74 Australasia (ANZ) [ANZ] 2 3 4 5 12 8 Winter Olympics Gold.1 Silver.1 Bronze.1 Total.1 # Games Gold.2 \ 90 0 0 0 0 0 13 0 101 3 0 0 0 0 15 5 112 18 0 0 0 0 41 18 123 6 0 0 0 0 11 1 134 0 0 0 0 0 2 3 14 Silver.2 Bronze.2 Combined total 150 0 2 2 161 2 8 15 172 24 28 70 183 2 9 12 194 4 5 12 ~~~ ## **Python数据清洗:回顾** 这个教程中,你学会了从数据集中如何使用`drop()`函数去除不必要的信息,也学会了如何为数据集设置索引,以让items可以被容易的找到。 更多的,你学会了如何使用`.str()`清洗对象字段,以及如何使用`applymap`对整个数据集清洗。最后,我们探索了如何移除CSV文件的行,并且使用`rename()`方法重命名列。 掌握数据清洗非常重要,因为它是数据科学的一个大的部分。你现在应该有了一个如何使用pandas和numpy进行数据清洗的基本理解了。更多内容可参考pandas和numpy官网。 > [https://realpython.com/python-data-cleaning-numpy-pandas/](https://realpython.com/python-data-cleaning-numpy-pandas/)