数据科学家花费大量时间清理数据集,将它们清理为可以工作的形式。事实上,很多数据科学家表示,80% 的工作都是获取和清理数据。
因此,不管你是刚刚进入这个领域或者计划进入,那么处理混乱数据的能力会非常重要,无论这意味着缺失值、格式不一致、格式错误还是无意义的异常值。
在此教程中,我们将利用 Pandas 和 NumPy 这两个库来清理数据。
我们将介绍以下内容:
- 删除
DataFrame
中不必要的列 - 更改
DataFrame
的索引 - 用
.str()
方法清理列 - 使用
DataFrame.applymap()
函数以元素方式清理数据集 - 将列重命名为更易识别的标签
- 跳过 CSV 文件中不必要的行
这些是我们将要用到的数据集:
- BL-Flickr-Images-Book.csv – 包含大英图书馆书籍信息的 CSV 文件
- university_towns.txt – 包含美国各州大学城名称的文本文件
- olympics.csv – 汇总所有国家夏季和冬季奥运会参与情况的 CSV 文件
你可以从 Real Python 的 GitHub 仓库 下载所有数据集,以便查看以下示例。
注意:我推荐使用 Jupyter Notebook 来进行以下步骤。
本教程假设你对 Pandas 和 NumPy 库有基本的了解,包括 Pandas 的主要工作对象 Series
和 DataFrame
,应用于它们的常用方法,以及熟悉 NumPy 的 NaN
值。
让我们从 import 这些模块开始吧!
1
2
>>> import pandas as pd
>>> import numpy as np
删除 DataFrame
中不必要的列
你经常会发现数据集中并非所有类别的数据都对你有用。例如,你可能有一个数据集包含了学生信息(名字、成绩、标准、父母姓名和住址),但你想要专注于分析学生的成绩。
在这种情况下,住址和父母姓名对你来说并不重要,保留这些类别将占用不必要的空间,并可能拖累运行时间。
Pandas 提供了一个很方便的 drop()
函数来从 DataFrame
中移除列或行。我们来看一个简单的例子,从 DataFrame
中删除一些列。
首先,我们从 CSV 文件 “BL-Flickr-Images-Book.csv” 中创建一个 DataFrame
。在下面的例子中,我们把相对路径传递给 pd.read_csv
,当前工作路径下,所有的数据集都存放在 Datasets
文件夹中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
>>> 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 Statement
,Corporate Author
,Corporate Contributors
,Former owner
,Engraver
,Issuance type
和 Shelfmarks
。
我们可以这样删除这些列:
1
2
3
4
5
6
7
8
9
10
>>> 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
,可以发现不想要的列已经被移除了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> 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
参数来删除列,不用单独指定删除的标签以及删除列还是行:
1
>>> df.drop(columns=to_drop, inplace=True)
这种方法更直观易读,这一步做了什么是非常明显的。
如果你事先知道那些列是你需要保留的,另外一个选择是将列作为 usecols
的参数传给 pd.read_csv
。
更改 DataFrame
的索引
Pandas 的 Index
扩展了 NumPy 的数组功能,从而可以实现更多功能的截取和标签。在多数情况下,使用数据唯一有价值的标识字段作为索引是很有帮助的。
例如,在上一节使用的数据集中,可以想象到,图书管理员如果需要搜索记录,他也许输入的是书籍的唯一标识符(Identifier
列):
1
2
>>> df['Identifier'].is_unique
True
让我们用 set_index
来替换现有的索引:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
>>> 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[]
可能不具有直观的名称,但它允许我们执行基于标签的索引,即标记某一行或某一条记录而不用考虑其位置:
1
2
3
4
5
6
7
8
>>> 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
参数来避免这种情况:
1
df.set_index('Identifier', inplace=True)
整理数据中的字段
到这里,我们已经删除了不必要的列,并将 DataFrame
的索引更改为更有意义的列。在这一节,我们将会清理特定的列,使其成为统一的格式,以便更好地理解数据集并强化一致性。具体来说,我们将清理 Date of Publication
和 Place of Publication
这两列。
经过检查,所有的数据类型都是 object
dtype,它与 Python 中的 str
类似。
它封装了任何不适用于数字或分类数据的字段。这是有道理的,因为我们使用的数据最初只是一堆杂乱的字符:
1
2
>>> df.get_dtype_counts()
object 6
其中出版日期一列,如果将其转化为数字类型更有意义,所以我们可以进行如下计算:
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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
综合以上,我们实际上可以利用一个正则表达式来提取出版年份:
1
regex = r'^(\d{4})'
这个正则表达式意图在字符串的开头找到四位数字,这足以满足我们的要求。上面是一个原始字符串(这意味着反斜杠不再是转义字符),这是正则表达式的标准做法。
\d
表示任何数字,{4}
表示重复 4 次,^
表示匹配字符串的开头,括号表示一个捕获组,它向 Pandas 表明我们想要提取正则表达式的这部分。(我们希望用 ^
来避免字符串从 [
开始的情况。)
现在让我们来看看我们在数据集中运行这个表达式时会发生什么:
1
2
3
4
5
6
7
8
9
>>> 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
即可轻松获取数字:
1
2
3
>>> df['Date of Publication'] = pd.to_numeric(extr)
>>> df['Date of Publication'].dtype
dtype('float64')
这么做会导致十分之一的值丢失,但这相对于能够对剩余的有效值上进行计算而已,是一个比较小的代价:
1
2
>>> 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()
宏的矢量化形式。它的语法如下:
1
>>> np.where(condition, then, else)
这里,condition
可以是一个类似数组的对象或者一个布尔遮罩,如果 condition
为 True
,则使用 then
值,否则使用 else
值。
从本质上来说,.where()
函数对对象中的每个元素进行检查,看 condition
是否为 True
,并返回一个 ndarray
对象,包含then
或者 else
的值。
它也可以被用于嵌套的 if-then 语句中,允许我们根据多个条件进行计算:
1
2
3
>>> np.where(condition1, x1,
np.where(condition2, x2,
np.where(condition3, x3, ...)))
我们将用这两个函数来清理 Place of Publication
一列,因为此列包含字符串。以下是该列的内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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’ 的行才会出现这种情况。
我们来看看两条特定的数据:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> 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()
来获得一个布尔掩码。
我们按如下方式清理此列:
1
2
3
4
5
6
7
8
9
10
11
12
>>> 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
结合:
1
2
3
4
5
6
7
8
9
10
11
12
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
的列。
虽然这个数据集中还有很多脏数据,我们现在只讨论这两列。
让我们来重新看看前五项,看起来比最开始的时候清晰多了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> 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
。
1
2
3
4
5
6
7
8
9
10
11
$ 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
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> 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 如下:
1
2
3
4
5
6
7
8
9
10
>>> 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 例子:
1
2
3
4
5
0 1
0 Mock Dataset
1 Python Pandas
2 Real Python
3 NumPy Clean
在这个例子中,每个单元格(‘Mock’、‘Dataset’、‘Python’、‘Pandas’ 等)都是一个元素。所以 applumap()
方法将函数作用于每个元素上。假设定义函数为:
1
2
3
4
5
6
7
>>> def get_citystate(item):
... if ' (' in item:
... return item[:item.find(' (')]
... elif '[' in item:
... return item[:item.find('[')]
... else:
... return item
Pandas 的 .applymap()
只接受一个参数,也就是将会作用于每个元素上的函数(可调用):
1
>>> towns_df = towns_df.applymap(get_citystate)
首先,我们定义一个 Python 函数,它以 DataFrame
中的元素作为参数。在函数内部,执行元素是否包含 (
或 [
的检查。
函数返回的值取决于这个检查。最后,applymap()
函数在我们的 DataFrame
对象上被调用。现在我们的 DataFrame
对象更加简洁了。
1
2
3
4
5
6
7
>>> 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 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> 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 行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> 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,映射到可用性更强的名字(字典值)。
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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()
函数:
1
>>> olympics_df.rename(columns=new_names, inplace=True)
将 inplace
参数设置为 True
可以将变化直接作用于我们的 DataFrame
对象上。让我们看看是否生效:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> 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 数据科学之旅:
- Pandas 文档
- NumPy 文档
- Python 数据分析 由 Pandas 的创造者 Wes McKinney 撰写
- Pandas Cookbook 由数据科学教练和顾问 Ted Petrou 撰写
Real Python 的每一个教程都是由一组开发人员创建,所以它符合我们的高质量标准。参与本教程的团队成员是 Malay Agarwal (作者)以及 Brad Solomon (编辑)。