[关闭]
@1007477689 2020-04-20T10:36:57.000000Z 字数 9936 阅读 689

Pandas数据清洗-学习

Python Pandas


(一)

I. 概要

PandasPython 中很流行的类库,使用它可以进行数据科学计算和数据分析。他可以联合其他数据科学计算工具一块儿使用,比如,SciPyNumPy 和Matplotlib,建模工程师可以通过创建端到端的分析工作流来解决业务问题。

虽然我们可以使用 Python 和数据分析做很多强大的事情,但是我们的分析结果的好坏依赖于数据的好坏。很多数据集存在数据缺失,或数据格式不统一(畸形数据),或错误数据的情况。不管是不完善的报表,还是技术处理数据的失当都会不可避免的引起“脏”数据。

庆幸的是,Pandas 提供功能强大的类库,不管数据处于什么状态,他可以帮助我们通过清洗数据,排序数据,最后得到清晰明了的数据。对于案例的数据,准备使用 movie_metadata.csv (链接:https://pan.baidu.com/s/1UfWB-vsBObiog3ZCiNJHmg 提取码:kkhc)。这个数据集包含了很多信息,演员、导演、预算、总输入,以及 IMDB 评分和上映时间。实际上,可以使用上百万或者更大的数据库,但是,案例数据集对于开始入门还是很好的。

不幸的是,有一些列的值是缺失的,有些列的默认值是“0”,有的是“NaN”(Not a Number)。

下面我们通过使用 Pandas 提供的功能来清洗“脏”数据。

II. 准备工作

首先,第一次使用 Pandas 之前,我们需要安装 Pandas。安装命令如下:

pip install pandas

接下来,导入 Pandas

到我们的代码中,代码如下:

  1. #可以使用其他的别名,但是,pd是官方推荐的别名,也是大家习惯的别名
  2. import pandas as pd

最后,加载数据集,代码如下:

  1. data = pd.read_csv('../data/tmdb_5000_credits.csv')

注意,确保已经下载数据集,如果你的代码和数据集的存放结构与我的一样,直接运行就可以

此处输入图片的描述

否则,要根据实际的情况,修改 pd.read_csv() 的文件路径

III. 检查数据

检查一下我们刚刚读入数据的基本结构,Pandas 提供了 .head() 方法打印输出前五行数据。目的是让我们对读入的数据有一个大致的了解。

  1. data.head()

此处输入图片的描述

我们可以通过上面介绍的 Pandas 的方法查看数据,也可以通过传统的 Excel 程序查看数据,这个时候,我们可以开始记录数据上的问题,然后,我们再想办法解决问题。

Pandas 提供了一些选择的方法,这些选择的方法可以把数据切片,也可以把数据切块。下面我们简单介绍一下:

IV. 处理缺失数据

缺失数据是最常见的问题之一。产生这个问题可能的原因

无论什么原因,只要有空白值得存在,就会引起后续的数据分析的错误。下面介绍几个处理缺失数据的方法:

1. 添加默认值

我们应该去掉那些不友好的 NaN 值。但是,我们应该用什么值替换呢?在这里,我们就应该稍微掌握一下数据。对于我们的例子,我们检查一下“country”列。这一列非常简单,然而有一些电影没有提供地区,所以有些数据的值是 NaN。在我们的案例中,我们推断地区并不是很重要,所以,我们可是使用“空字符串”("")或其他默认值。

  1. data.country = data.country.fillna('')

上面,我们就将“country”整个列使用“”空字符串替换了,或者,我们也可以轻易地使用“None Given”这样的默认值进行替换。如果想了解更多 .fillna() 的详细信息参考 pandas.DataFrame.fillna

使用“数字类型”的数据,比如,电影的时长,计算像电影平均时长可以帮我们甚至是数据集。这并不是最优解,但这个持续时间是根据其他数据估算出来的。这样的方式下,就不会因为像 0 或者 NaN 这样的值在我们分析的时候而抛错。

  1. data.duration = data.duration.fillna(data.duration.mean())

2. 删除不完整的行

假设:我们想删除任何有缺失值的行。这种操作太据侵略性,但是我们可以根据我们的需要进行扩展。

i. 删除任何包含 NA 值的行是很容易的:

  1. data.dropna()

ii. 我们也可以删除一整行值都为 NA的行:

  1. data.dropna(how = 'all')

我们也可以增加一些限制:在一行中有多少非空值的数据是可以保留下来的

iii. 删除数据中至少要有 5 个非空值的行

  1. data.drop(thresh = 5)

比如说,我们不想要不知道电影上映时间的数据:

  1. data.dropna(subset = ['title_year'])

上面的 subset 参数允许我们选择想要检查的列。如果是多个列,可以使用列名的 list 作为参数。

3. 删除不完整的列

我们可以上面的操作应用到列上。我们仅仅需要在代码上使用 axis = 1 参数。这个意思就是操作列而不是行。(我们已经在行的例子中使用了 axis = 0 ,因为如果我们不传参数 axis ,默认是 axis = 0。)

i. 删除一整列为 NaN 的列:

  1. data.drop(axis = 1, how = 'all')

ii. 删除任何包含空值的列:

  1. data.drop(axis = 1 how = 'any')

这里也可以使用像上面一样的 thresholdsubset,更多的详情和案例,请参考pandas.DataFrame.dropna

V. 规范化数据类型

有的时候,尤其当我们读取 .csv 文件中的一串数字的时候,有的时候“数值类型”的数字被读成“字符串”的数字,或将字符串的数字读成数据值类型的数字。Pandas 还是提供了规范化我们数据类型的方式:

  1. data = pd.read_csv('../data/moive_metadata.csv', dtype = {'duration': int})

这就是告诉 Pandas ‘duration’ 列的类型是“数值类型”。同样的,如果想把上映年份读成“字符串”而不是“数值”类型,我们使用和上面类似的方法:

  1. data = pd.read_csv('./data/moive_metadata.csv', dtype = {'title_year': str})

注意,需要记住的是,再次从磁盘上读取 csv,确保规范化了我们的数据类型,或者在读取之前已经保存了中间结果。

VI. 必要的变换

人工录入的数据可能都需要进行一些必要的变换。

将我们数据中所有的 movie_title 改成“大写”:

  1. data['movie_title'].str.upper()

同样的,“干掉末尾空格”:

  1. data['movie_title'].str.strip()

这里并没有介绍关于英文的拼写错误的问题,可以参考“模糊匹配”(https://github.com/seatgeek/fuzzywuzzy)。

VII. 重命名列名

最终的数据可能是有计算机生成的,那么,列名有可能也是计算机按照一定计算规律生成的。这些列名对计算机没有什么,但是对于人来说可能就不够友好,这时候,我们就需要重命名成对人友好的列名,代码如下:

  1. columns_reset = {‘title_year’: release_date’,
  2. movie_facebook_likes’: facebook_likes’}
  3. data.rename(columns = columns_reset)

像上面这样,我们就完成了两个列的重命名。需要注意的是,这个方法并没有提供 inpalce 参数,我们需要将结果赋值给自己才可以:

  1. columns_reset = {‘title_year’: release_date’,
  2. movie_facebook_likes’: facebook_likes’}
  3. data = data.rename(columns = columns_reset)

VIII. 保存结果

我们完成数据清洗之后,一般会把结果再以 .csv 的格式保存下来,以便后续其他程序的处理。同样,Pandas 提供了非常易用的方法:

  1. data.to_csv("cleanfile.csv", encoding = utf-8’)

IX. 更多资源

这次介绍仅仅是冰山一角。有很多方式可能造成数据集变“脏”或被破坏:

在这里,我们介绍了 PythonPandas 清洗数据最一般的方式。

(二)

I. 了解数据

在处理任何数据之前,我们的第一任务是理解数据以及数据是干什么用的。我们尝试去理解数据的列/行、记录、数据格式、语义错误、缺失的条目以及错误的格式,这样我们就可以大概了解数据分析之前要做哪些“清理”工作。

本次我们需要一个 patient_heart_rate.csv (链接:https://pan.baidu.com/s/1l-nk1VYF0u9cyponldD40A 密码:7r53)的数据文件,这个数据很小,可以让我们一目了然。这个数据是 csv 格式。数据是描述不同个体在不同时间的心跳情况。数据的列信息包括人的年龄、体重、性别和不同时间的心率。

  1. import pandas as pd
  2. df = pd.read_csv('../data/patient_heart_rate.csv')
  3. df.head()

此处输入图片的描述

II. 分析数据问题

下面我们就针对上面的问题一一击破。

III. 清洗数据

1. 没有列头

如果我们拿到的数据像上面的数据一样没有列头,Pandas 在读取 csv 提供了自定义列头的参数。下面我们就通过手动设置列头参数来读取 csv,代码如下:

  1. import pandas as pd
  2. # 增加列头
  3. column_names = ['id', 'name', 'age', 'weight', 'm0006', 'm0612', 'm1218', 'f0006', 'f0612', 'f1218']
  4. df = pd.read_csv('../data/patient_heart_rate.csv', names = column_names)
  5. df.head()

此处输入图片的描述

上面的结果展示了我们自定义的列头。我们只是在这次读取 csv 的时候,多了传了一个参数 names = column_names,这个就是告诉 Pandas 使用我们提供的列头。

2. 一个列有多个参数

在数据中不难发现,Name 列包含了两个参数 FirtnameLastname。为了达到数据整洁目的,我们决定将 name 列拆分成 Firstname 列和 Lastname 列。

从技术角度,我们可以使用 .split 方法,完成拆分工作。

我们使用 str.split(expand = True),将列表拆成新的列,再将原来的 Name 列删除。

  1. # 切分名字,删除源数据列
  2. df[['first_name', 'last_name']] = df['name'].str.split(expand = True)
  3. df.drop('name', axis = 1, inplace = True)

此处输入图片的描述

上面就是执行执行代码之后的结果。

3. 列数据的单位不统一

如果仔细观察数据集可以发现 Weight 列的单位不统一。有的单位是 “kgs”,有的单位是 “lbs”。

  1. # 获取 weight 数据列中单位为 lbs 的数据
  2. rows_with_lbs = df['weight'].str.contains('lbs').fillna(False)
  3. df[rows_with_lbs]

此处输入图片的描述

为了解决这个问题,将单位统一,我们将单位是 lbs 的数据转换成 kgs。

  1. # 将 lbs 的数据转换为 kgs 数据
  2. for i, lbs_row in df[rows_with_lbs].iterrows():
  3. weight = int(float(lbs_row['weight'][:-3])/2.2)
  4. df.at[i,'weight'] = f'{weight}kgs'

此处输入图片的描述

4. 缺失值

在数据集中有些年龄、体重、心率是缺失的。我们又遇到了数据清洗最常见的问题——数据缺失。一般是因为没有收集到这些信息。我们可以咨询行业专家的意见。典型的处理缺失数据的方法:

5. 空行

仔细对比我们会发现数据中一行空行,除了 index 之外,全部的值都是 NaN

Pandasread_csv() 并没有可选参数来忽略空行,这样,我们就需要在数据被读入之后再使用 dropna() 进行处理,删除空行.

  1. # 删除全空的行
  2. df.dropna(how = 'all', inplace = True)

此处输入图片的描述

6. 重复数据

有的时候数据集中会有一些重复的数据。在我们的数据集中也添加了重复的数据。

此处输入图片的描述

首先,我们校验一下是否存在重复记录。如果存在重复记录,就使用 Pandas 提供的 drop_duplicates() 来删除重复数据。

  1. # 删除重复数据行
  2. df.drop_duplicates(subset = ['first_name', 'last_name'], inplace = True)

此处输入图片的描述

7. 非 ASCII 字符

在数据集中 FristnameLastname 有一些非 ASCII 的字符。

此处输入图片的描述

处理非 ASCII 数据方式有多种

我们使用删除的方式:

  1. # 删除非 ASCII 字符
  2. df['first_name'].replace({r'[^\x00-\x7F]+':''}, regex = True, inplace = True)
  3. df['last_name'].replace({r'[^\x00-\x7F]+':''}, regex = True, inplace = True)

此处输入图片的描述

8. 有些列头应该是数据,而不应该是列名参数

有一些列头是有性别和时间范围组成的,这些数据有可能是在处理收集的过程中进行了行列转换,或者收集器的固定命名规则。这些值应该被分解为性别(m,f),小时单位的时间范围(00-06,06-12,12-18)

# 切分 sex_hour 列为 sex 列和 hour 列
sorted_columns = ['id', 'age', 'weight', 'first_name', 'last_name']

df = pd.melt(df, id_vars = sorted_columns, var_name = 'sex_hour', value_name = 'puls_rate').sort_values(sorted_columns)

f = lambda x: pd.Series(([x[:1], f'{x[1:3]}-{x[3:]}']))

df[['sex','hour']] = df['sex_hour'].apply(f)[[0,1]]

df.drop('sex_hour', axis = 1, inplace = True)
# 删除没有心率的数据
row_with_dashes = df['puls_rate'].str.contains('-').fillna(False)
df.drop(df[row_with_dashes].index, inplace = True)

# 重置索引,不做也没关系,主要是为了看着美观一点
df = df.reset_index(drop = True)
print(df)

此处输入图片的描述

III. 整合代码

  1. import pandas as pd
  2. # 增加列头
  3. column_names= ['id', 'name', 'age', 'weight','m0006','m0612','m1218','f0006','f0612','f1218']
  4. df = pd.read_csv('../data/patient_heart_rate.csv', names = column_names)
  5. # 切分名字,删除源数据列
  6. df[['first_name','last_name']] = df['name'].str.split(expand=True)
  7. df.drop('name', axis=1, inplace=True)
  8. # 获取 weight 数据列中单位为 lbs 的数据
  9. rows_with_lbs = df['weight'].str.contains('lbs').fillna(False)
  10. df[rows_with_lbs]
  11. # 将 lbs 的数据转换为 kgs 数据
  12. for i,lbs_row in df[rows_with_lbs].iterrows():
  13. weight = int(float(lbs_row['weight'][:-3])/2.2)
  14. df.at[i,'weight'] = '{}kgs'.format(weight)
  15. # 删除全空的行
  16. df.dropna(how='all',inplace=True)
  17. # 删除重复数据行
  18. df.drop_duplicates(['first_name','last_name'],inplace=True)
  19. # 删除非 ASCII 字符
  20. df['first_name'].replace({r'[^\x00-\x7F]+':''}, regex=True, inplace=True)
  21. df['last_name'].replace({r'[^\x00-\x7F]+':''}, regex=True, inplace=True)
  22. # 切分 sex_hour 列为 sex 列和 hour 列
  23. sorted_columns = ['id','age','weight','first_name','last_name']
  24. df = pd.melt(df,
  25. id_vars=sorted_columns,var_name='sex_hour',value_name='puls_rate').sort_values(sorted_columns)
  26. df[['sex','hour']] = df['sex_hour'].apply(lambda x:pd.Series(([x[:1],'{}-{}'.format(x[1:3],x[3:])])))[[0,1]]
  27. df.drop('sex_hour', axis=1, inplace=True)
  28. # 删除没有心率的数据
  29. row_with_dashes = df['puls_rate'].str.contains('-').fillna(False)
  30. df.drop(df[row_with_dashes].index,
  31. inplace=True)
  32. # 重置索引,不做也没关系,主要是为了看着美观一点
  33. df = df.reset_index(drop=True)
  34. print(df)

还有一些问题在本例中没有提及内容,下面有两个比较重要,也比较通用的问题:

日期的处理
字符编码的问题

本次又介绍了一些关于 Pandas 清洗数据的技能。至少用这几次介绍的处理方法,应该可以对数据做很多清洗工作。

(三)

I. 预览数据

这次我们使用 Artworks.csv ,我们选取 100 行数据来完成本次内容。具体步骤:

  1. 导入 Pandas
  2. 读取 csv 数据到 DataFrame(要确保数据已经下载到指定路径)

DataFramePandas 中内置的数据展示的结构,展示速度很快,通过 DataFrame 我们就可以快速的预览和分析数据。代码如下:

  1. import pandas as pd
  2. df = pd.read_csv('../data/Artworks.csv').head(100)
  3. df.head(10)

此处输入图片的描述

II. 统计日期数据

我们仔细观察一下 Date 列的数据,有一些数据是“年的范围”(1976-1977),而不是单独的一个年份。在我们使用年份数据画图时,就不能像单独的年份那样轻易的画出来。我们现在就使用 Pandas.value_counts() 来统计一下每种数据的数量。

首先,选择要统计的列,并调用 .value_counts():

  1. df['Date'].value_counts()

此处输入图片的描述

III. 日期数据问题

Date 列数据,除了年份是范围外,还有三种非正常格式。下面我们将这几种列出来:

接下来我们会处理上面的每一个问题,使用 Pandas 将这些不规则的数据转换为统一格式的数据。

“问题一”和“问题二”是有数据的只是格式上欠妥当,“问题三”和“问题四”实际上不是有效数据。针对前两个问题,我们可以通过代码将据格式化来达到清洗的目的然而,后两个问题,代码上只能将其作为缺失值来处理。简单起见,我们将问题三和问题四的数据处理为0。

1. 处理问题一

问题一的数据都是两个年时间范围,我们选择其中的一个年份作为清洗之后的数据。为了简单起见,我们就使用开始的时间来替换这样问题的数据,因为这个时间是一个四位数的数字,如果要使用结束的年份,我们还要补齐前两位的数字。

首先,我们需要找到问题一的数据,这样我们才能将其更新。要保证其他的数据不被更新,因为其他的数据有可能是已经格式化好的,也有可能是我们下面要处理的。

我们要处理的时间范围的数据,其中包含有“-”,这样我们就可以通过这个特殊的字符串来过滤我们要处理的数据,然后,通过 split() 利用“-”将数据分割,将结果的第一部分作为处理的最终结果。

代码如下

  1. row_with_dashes = df['Date'].str.contains('-').fillna(False)
  2. for i, dash in df[row_with_dashes].iterrows():
  3. df.at[i,'Date'] = dash['Date'][0:4]
  4. df['Date'].value_counts()

2. 处理问题二

问题二的数据体现了数据本身的不准确性,是一个估计的年份时间,我们将其转换为年份,那么,就只要保留最后四位数字即可,该数据的特点就是数据包含“c”,这样我们就可以通过这一特征将需要转换的数据过滤出来。

  1. row_with_cs = df['Date'].str.contains('c').fillna(False)
  2. for i, row in df[row_with_cs].iterrows():
  3. df.at[i,'Date'] = row['Date'][-4:]
  4. df[row_with_cs]

3. 处理问题三、四

将这问题三四的数据赋值成初始值 0。

  1. df['Date'] = df['Date'].replace('Unknown', '0', regex = True)
  2. df['Date'] = df['Date'].replace('n.d.', '0', regex =True)
  3. df['Date']

此处输入图片的描述

IV. 代码整合

  1. import pandas as pd
  2. df = pd.read_csv('../data/Artworks.csv').head(100)
  3. df.head(10)
  4. df['Date'].value_counts()
  5. row_with_dashes = df['Date'].str.contains('-').fillna(False)
  6. for i, dash in df[row_with_dashes].iterrows():
  7. df.at[i,'Date'] = dash['Date'][0:4]
  8. df['Date'].value_counts()
  9. row_with_cs = df['Date'].str.contains('c').fillna(False)
  10. for i,row in df[row_with_cs].iterrows():
  11. df.at[i,'Date'] = row['Date'][-4:]
  12. df['Date'].value_counts()
  13. df['Date'] = df['Date'].replace('Unknown','0',regex=True)
  14. df['Date'] = df['Date'].replace('n.d.','0',regex=True)
  15. df['Date'].value_counts()
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注