四、数据清理

原文:DS-100/textbook/notebooks/ch04

译者:飞龙

协议:CC BY-NC-SA 4.0

自豪地采用谷歌翻译

数据以多种格式出现,并且在分析的实用性方面差别很大。尽管我们希望,我们所有的数据都以表格的形式出现,并且每个数值的记录都一致和准确,但实际上,我们必须仔细检查数据,找出最终可能导致错误结论的潜在问题。

术语“数据清理”是指梳理数据,并决定如何解决不一致和缺失值的过程。我们将讨论数据集中发现的常见问题,以及解决这些问题的方法。

数据清理存在固有的局限性。例如,没有任何数据清理能够解决带偏差的采样过程。在着手进行有时很长的数据清理过程之前,我们必须保证,我们的数据是准确收集的,尽可能没有偏差。只有这样,我们才能调查数据本身,并使用数据清理来解决数据格式或输入过程中的问题。

我们将通过处理伯克利市警察数据集,介绍数据清理技术。

调查伯克利警察数据

我们将使用伯克利警察局的公开数据集,来演示数据清理技术。 我们已经下载了服务呼叫数据集和截停数据集。

我们可以使用带有-lh标志的ls shell 命令,来查看这些文件的更多详细信息:

!ls -lh data/total 13936
-rw-r--r--@ 1 sam  staff   979K Aug 29 14:41 Berkeley_PD_-_Calls_for_Service.csv
-rw-r--r--@ 1 sam  staff    81B Aug 29 14:28 cvdow.csv
-rw-r--r--@ 1 sam  staff   5.8M Aug 29 14:41 stops.json

上面的命令显示了数据文件及其文件大小。 这是特别有用的,因为我们现在知道这些文件足够小,可以加载到内存中。 作为一个经验法则,将文件加载到内存中,内存大约占计算机总内存容量的四分之一,通常是安全的。 例如,如果一台电脑有 4GB 的 RAM ,我们应该可以在pandas中加载 1GB 的 CSV 文件。 为了处理更大的数据集,我们需要额外的计算工具,我们将在本书后面介绍。

注意在ls之前使用感叹号。 这告诉 Jupyter 下一行代码是 shell 命令,而不是 Python 表达式。 我们可以使用!在 Jupyter 中运行任何可用的 shell 命令:

# The `wc` shell command shows us how many lines each file has.
# We can see that the `stops.json` file has the most lines (29852).
!wc -l data/*16497 data/Berkeley_PD_-_Calls_for_Service.csv8 data/cvdow.csv29852 data/stops.json46357 total

理解数据生成

在数据清理或处理之前,我们将陈述你应该向所有数据集询问的重要问题。 这些问题与数据的生成方式有关,因此数据清理通常无法解决这里出现的问题。

数据包含什么内容? 服务呼叫数据的网站指出,该数据集描述了“过去 180 天内的犯罪事件(而非犯罪报告)”。 进一步阅读表明“并非所有警务服务的呼叫都包含在内(例如动物咬伤)”。

截停数据的网站指出,该数据集包含自 2015 年 1 月 26 日起的所有“车辆截停(包括自行车)和行人截停(最多五人)”的数据。

数据是普查吗? 这取决于我们感兴趣的人群。 例如,如果我们感兴趣的是,过去 180 天内的犯罪事件的服务呼叫,那么呼叫数据集就是一个普查。 但是,如果我们感兴趣的是,过去 10 年内的服务呼叫,数据集显然不是普查。 由于数据收集开始于 2015 年 1 月 26 日,我们可以对截停数据集做出类似的猜测。

如果数据构成一个样本,它是概率样本吗? 如果我们正在调查一个时间段,数据没有它的条目,那么数据不会形成概率样本,因为在数据收集过程中没有涉及随机性 - 我们有一定时间段的所有数据,但其他时间段没有数据。

这些数据对我们的结论有何限制? 虽然我们会在数据处理的每一步都提出这个问题,但我们已经可以看到,我们的数据带有重要的限制。 最重要的限制是,我们不能对我们的数据集未涵盖的时间段进行无偏估计。

清理呼叫数据集

现在我们来清理呼叫数据集。head shell 命令打印文件的前五行。

!head data/Berkeley_PD_-_Calls_for_Service.csvCASENO,OFFENSE,EVENTDT,EVENTTM,CVLEGEND,CVDOW,InDbDate,Block_Location,BLKADDR,City,State
17091420,BURGLARY AUTO,07/23/2017 12:00:00 AM,06:00,BURGLARY - VEHICLE,0,08/29/2017 08:28:05 AM,"2500 LE CONTE AVE
Berkeley, CA
(37.876965, -122.260544)",2500 LE CONTE AVE,Berkeley,CA
17020462,THEFT FROM PERSON,04/13/2017 12:00:00 AM,08:45,LARCENY,4,08/29/2017 08:28:00 AM,"2200 SHATTUCK AVE
Berkeley, CA
(37.869363, -122.268028)",2200 SHATTUCK AVE,Berkeley,CA
17050275,BURGLARY AUTO,08/24/2017 12:00:00 AM,18:30,BURGLARY - VEHICLE,4,08/29/2017 08:28:06 AM,"200 UNIVERSITY AVE
Berkeley, CA
(37.865491, -122.310065)",200 UNIVERSITY AVE,Berkeley,CA

它似乎是逗号分隔值(CSV)文件,尽管很难判断整个文件是否格式正确。 我们可以使用pd.read_csv将文件读取为DataFrame。 如果pd.read_csv产生错误,我们将不得不更进一步并手动解决格式问题。 幸运的是,pd.read_csv成功返回一个DataFrame

calls = pd.read_csv('data/Berkeley_PD_-_Calls_for_Service.csv')
calls
CASENO OFFENSE EVENTDT EVENTTM Block_Location BLKADDR City State
0 17091420 BURGLARY AUTO 07/23/2017 12:00:00 AM 06:00 2500 LE CONTE AVE\nBerkeley, CA\n(37.876965, -… 2500 LE CONTE AVE Berkeley
1 17020462 THEFT FROM PERSON 04/13/2017 12:00:00 AM 08:45 2200 SHATTUCK AVE\nBerkeley, CA\n(37.869363, -… 2200 SHATTUCK AVE Berkeley
2 17050275 BURGLARY AUTO 08/24/2017 12:00:00 AM 18:30 200 UNIVERSITY AVE\nBerkeley, CA\n(37.865491, … 200 UNIVERSITY AVE Berkeley
5505 17018126 DISTURBANCE 04/01/2017 12:00:00 AM 12:22 1600 FAIRVIEW ST\nBerkeley, CA\n(37.850001, -1… 1600 FAIRVIEW ST Berkeley
5506 17090665 THEFT MISD. (UNDER $950) 04/01/2017 12:00:00 AM 12:00 2000 DELAWARE ST\nBerkeley, CA\n(37.874489, -1… 2000 DELAWARE ST Berkeley
5507 17049700 SEXUAL ASSAULT MISD. 08/22/2017 12:00:00 AM 20:02 2400 TELEGRAPH AVE\nBerkeley, CA\n(37.866761, … 2400 TELEGRAPH AVE Berkeley

5508 行 × 11 列

我们可以定义一个函数来显示数据的不同片段,然后与之交互:

def df_interact(df):'''Outputs sliders that show rows and columns of df'''def peek(row=0, col=0):return df.iloc[row:row + 5, col:col + 6]interact(peek, row=(0, len(df), 5), col=(0, len(df.columns) - 6))print('({} rows, {} columns) total'.format(df.shape[0], df.shape[1]))df_interact(calls)
# (5508 rows, 11 columns) total

根据上面的输出结果,生成的DataFrame看起来很合理,因为列的名称正确,每列中的数据看起来都是一致的。 每列包含哪些数据? 我们可以查看数据集网站:

描述 类型
CASENO 案件编号 数字
OFFENSE 案件类型 纯文本
EVENTDT 事件的发生日期 日期时间
EVENTTM 事件的发生时间 纯文本
CVLEGEND 事件描述 纯文本
CVDOW 时间的发生星期 数字
InDbDate 数据集的上传日期 日期时间
Block_Location 事件的街区级别的地址 地点
BLKADDR 纯文本
City 纯文本
State 纯文本

数据表面上看起来很容易处理。 但是,在开始数据分析之前,我们必须回答以下问题:

数据集中是否存在缺失值? 这个问题很重要,因为缺失值可能代表许多不同的事情。 例如,遗漏的地址可能意味着删除了地点来保护隐私,或者某些受访者选择不回答调查问题,或录制设备损坏。
是否有已填写的缺失值(例如 999 岁,未知年龄,或上午 12:00 为未知日期)? 如果我们忽略它们,它们显然将影响分析。
数据的哪些部分是由人类输入的? 我们将很快看到,人类输入的数据充满了不一致和错误拼写。
虽然要通过更多检查,但这三种检查方法在很多情况下都足够了。 查看 Quartz 的不良数据指南,来获取更完整的检查列表。

是否存在缺失值?

pandas中这是一个简单的检查:

# True if row contains at least one null value
null_rows = calls.isnull().any(axis=1)
calls[null_rows]
CASENO OFFENSE EVENTDT EVENTTM Block_Location BLKADDR City State
116 17014831 BURGLARY AUTO 03/16/2017 12:00:00 AM 22:00 Berkeley, CA\n(37.869058, -122.270455) NaN Berkeley
478 17042511 BURGLARY AUTO 07/20/2017 12:00:00 AM 16:00 Berkeley, CA\n(37.869058, -122.270455) NaN Berkeley
486 17022572 VEHICLE STOLEN 04/22/2017 12:00:00 AM 21:00 Berkeley, CA\n(37.869058, -122.270455) NaN Berkeley
4945 17091287 VANDALISM 07/01/2017 12:00:00 AM 08:00 Berkeley, CA\n(37.869058, -122.270455) NaN Berkeley
4947 17038382 BURGLARY RESIDENTIAL 06/30/2017 12:00:00 AM 15:00 Berkeley, CA\n(37.869058, -122.270455) NaN Berkeley
5167 17091632 VANDALISM 08/15/2017 12:00:00 AM 23:30 Berkeley, CA\n(37.869058, -122.270455) NaN Berkeley

27 行 × 11 列

看起来BLKADDR中有 27 个呼叫没有地址记录。 不幸的是,对于地点的记录方式,数据描述并不十分清楚。 我们知道,所有这些呼叫都是由于伯克利的事件,因此我们可以认为,这些呼叫的地址最初是在伯克利的某个地方。

有没有已填充的缺失值?

从上面的缺失值检查中,我们可以看到,如果位置缺失,Block_Location列会记录Berkeley, CA

另外,通过查看呼叫表,我们发现EVENTDT列日期正确,但所有时间都记录了上午 12 点。 相反,时间在EVENTTM列中。

# Show the first 7 rows of the table again for reference
calls.head(7)
CASENO OFFENSE EVENTDT EVENTTM Block_Location BLKADDR City State
0 17091420 BURGLARY AUTO 07/23/2017 12:00:00 AM 06:00 2500 LE CONTE AVE\nBerkeley, CA\n(37.876965, -… 2500 LE CONTE AVE Berkeley
1 17020462 THEFT FROM PERSON 04/13/2017 12:00:00 AM 08:45 2200 SHATTUCK AVE\nBerkeley, CA\n(37.869363, -… 2200 SHATTUCK AVE Berkeley
2 17050275 BURGLARY AUTO 08/24/2017 12:00:00 AM 18:30 200 UNIVERSITY AVE\nBerkeley, CA\n(37.865491, … 200 UNIVERSITY AVE Berkeley
3 17019145 GUN/WEAPON 04/06/2017 12:00:00 AM 17:30 1900 SEVENTH ST\nBerkeley, CA\n(37.869318, -12… 1900 SEVENTH ST Berkeley
4 17044993 VEHICLE STOLEN 08/01/2017 12:00:00 AM 18:00 100 PARKSIDE DR\nBerkeley, CA\n(37.854247, -12… 100 PARKSIDE DR Berkeley
5 17037319 BURGLARY RESIDENTIAL 06/28/2017 12:00:00 AM 12:00 1500 PRINCE ST\nBerkeley, CA\n(37.851503, -122… 1500 PRINCE ST Berkeley
6 17030791 BURGLARY RESIDENTIAL 05/30/2017 12:00:00 AM 08:45 300 MENLO PL\nBerkeley, CA\n 300 MENLO PL Berkeley

7 行 × 11 列

作为数据清理步骤,我们希望合并EVENTDTEVENTTM列,在一个字段中记录日期和时间。 如果我们定义一个函数,接受DF并返回新的DF,我们可以稍后使用pd.pipe一次性应用所有转换。

def combine_event_datetimes(calls):combined = pd.to_datetime(# Combine date and time stringscalls['EVENTDT'].str[:10] + ' ' + calls['EVENTTM'],infer_datetime_format=True,)return calls.assign(EVENTDTTM=combined)# To peek at the result without mutating the calls DF:
calls.pipe(combine_event_datetimes).head(2)
CASENO OFFENSE EVENTDT EVENTTM BLKADDR City State EVENTDTTM
0 17091420 BURGLARY AUTO 07/23/2017 12:00:00 AM 06:00 2500 LE CONTE AVE Berkeley CA
1 17020462 THEFT FROM PERSON 04/13/2017 12:00:00 AM 08:45 2200 SHATTUCK AVE Berkeley CA

2 行 × 12 列

数据的哪些部分是由人类输入的?

看起来,大多数数据列是机器记录的,包括日期,时间,星期和事件位置。

另外,OFFENSECVLEGEND列看起来包含一致的值。 我们可以检查每列中的唯一值,来查看是否有任何拼写错误:

calls['OFFENSE'].unique()
'''
array(['BURGLARY AUTO', 'THEFT FROM PERSON', 'GUN/WEAPON','VEHICLE STOLEN', 'BURGLARY RESIDENTIAL', 'VANDALISM','DISTURBANCE', 'THEFT MISD. (UNDER $950)', 'THEFT FROM AUTO','DOMESTIC VIOLENCE', 'THEFT FELONY (OVER $950)', 'ALCOHOL OFFENSE','MISSING JUVENILE', 'ROBBERY', 'IDENTITY THEFT','ASSAULT/BATTERY MISD.', '2ND RESPONSE', 'BRANDISHING','MISSING ADULT', 'NARCOTICS', 'FRAUD/FORGERY','ASSAULT/BATTERY FEL.', 'BURGLARY COMMERCIAL', 'MUNICIPAL CODE','ARSON', 'SEXUAL ASSAULT FEL.', 'VEHICLE RECOVERED','SEXUAL ASSAULT MISD.', 'KIDNAPPING', 'VICE', 'HOMICIDE'], dtype=object)
'''
calls['CVLEGEND'].unique()
'''
array(['BURGLARY - VEHICLE', 'LARCENY', 'WEAPONS OFFENSE','MOTOR VEHICLE THEFT', 'BURGLARY - RESIDENTIAL', 'VANDALISM','DISORDERLY CONDUCT', 'LARCENY - FROM VEHICLE', 'FAMILY OFFENSE','LIQUOR LAW VIOLATION', 'MISSING PERSON', 'ROBBERY', 'FRAUD','ASSAULT', 'NOISE VIOLATION', 'DRUG VIOLATION','BURGLARY - COMMERCIAL', 'ALL OTHER OFFENSES', 'ARSON', 'SEX CRIME','RECOVERED VEHICLE', 'KIDNAPPING', 'HOMICIDE'], dtype=object)
'''

由于这些列中的每个值似乎都拼写正确,因此我们不必对这些列执行任何更正。

我们还检查了BLKADDR列的不一致性,发现有时记录了地址(例如2500LE CONTE AVE),但有时记录十字路口(例如ALLSTON WAY & FIFTH ST)。 这表明人类输入了这些数据,而这一栏很难用于分析。 幸运的是,我们可以使用事件的经纬度而不是街道地址。

calls['BLKADDR'][[0, 5001]]
'''
0            2500 LE CONTE AVE
5001    ALLSTON WAY & FIFTH ST
Name: BLKADDR, dtype: object
'''

最后的接触

这个数据集似乎几乎可用于分析。 Block_Location列似乎包含记录地址,纬度和经度的字符串。 我们将要分割经纬度以便使用。

def split_lat_lon(calls):return calls.join(calls['Block_Location']# Get coords from string.str.split('\n').str[2]# Remove parens from coords.str[1:-1]# Split latitude and longitude.str.split(', ', expand=True).rename(columns={0: 'Latitude', 1: 'Longitude'}))calls.pipe(split_lat_lon).head(2)
CASENO OFFENSE EVENTDT EVENTTM City State Latitude Longitude
0 17091420 BURGLARY AUTO 07/23/2017 12:00:00 AM 06:00 Berkeley CA 37.876965
1 17020462 THEFT FROM PERSON 04/13/2017 12:00:00 AM 08:45 Berkeley CA 37.869363

2 行 × 13 列

然后,我们可以将星期序号与星期进行匹配:

# This DF contains the day for each 数字 in CVDOW
day_of_week = pd.read_csv('data/cvdow.csv')
day_of_week
CVDOW Day
0 0
1 1
2 2
3 3
4 4
5 5
6 6
def match_weekday(calls):return calls.merge(day_of_week, on='CVDOW')
calls.pipe(match_weekday).head(2)
CASENO OFFENSE EVENTDT EVENTTM BLKADDR City State Day
0 17091420 BURGLARY AUTO 07/23/2017 12:00:00 AM 06:00 2500 LE CONTE AVE Berkeley CA
1 17038302 BURGLARY AUTO 07/02/2017 12:00:00 AM 22:00 BOWDITCH STREET & CHANNING WAY Berkeley CA

2 行 × 12 列

我们将删除我们不再需要的列:

def drop_unneeded_cols(calls):return calls.drop(columns=['CVDOW', 'InDbDate', 'Block_Location', 'City','State', 'EVENTDT', 'EVENTTM'])

最后,我们让calls DF 穿过我们定义的所有函数的管道:

calls_final = (calls.pipe(combine_event_datetimes).pipe(split_lat_lon).pipe(match_weekday).pipe(drop_unneeded_cols))
df_interact(calls_final)

户籍数据集现在可用于进一步的数据分析。 在下一节中,我们将清理截停数据集。

# HIDDEN
# Save data to CSV for other chapters
# calls_final.to_csv('../ch5/data/calls.csv', index=False)

清理截停数据集

截停数据集记录警察截停的行人和车辆。 让我们准备进一步分析。

我们可以使用head命令来显示文件的前几行。

!head data/stops.json{"meta" : {"view" : {"id" : "6e9j-pj9p","name" : "Berkeley PD - Stop Data","attribution" : "Berkeley Police Department","averageRating" : 0,"category" : "Public Safety","createdAt" : 1444171604,"description" : "This data was extracted from the Department’s Public Safety Server and covers the data beginning January 26, 2015.  On January 26, 2015 the department began collecting data pursuant to General Order B-4 (issued December 31, 2014).  Under that order, officers were required to provide certain data after making all vehicle detentions (including bicycles) and pedestrian detentions (up to five persons).  This data set lists stops by police in the categories of traffic, suspicious vehicle, pedestrian and bicycle stops.  Incident number, date and time, location and disposition codes are also listed in this data.\r\n\r\nAddress data has been changed from a specific address, where applicable, and listed as the block where the incident occurred.  Disposition codes were entered by officers who made the stop.  These codes included the person(s) race, gender, age (range), reason for the stop, enforcement action taken, and whether or not a search was conducted.\r\n\r\nThe officers of the Berkeley Police Department are prohibited from biased based policing, which is defined as any police-initiated action that relies on the race, ethnicity, or national origin rather than the behavior of an individual or information that leads the police to a particular individual who has been identified as being engaged in criminal activity.",

stops.json文件显然不是 CSV 文件。 在这种情况下,该文件包含 JSON(JavaScript 对象表示法)格式的数据,这是一种常用的数据格式,其中数据记录为字典格式。 Python 的json模块使得该文件可以简单地读取为字典。

import json# Note that this could cause our computer to run out of memory if the file
# is large. In this case, we've verified that the file is small enough to
# read in beforehand.
with open('data/stops.json') as f:stops_dict = json.load(f)stops_dict.keys()
# dict_keys(['meta', 'data'])

请注意,stops_dict是一个 Python 字典,因此显示它将在笔记本中显示整个数据集。 这可能会导致浏览器崩溃,所以我们只显示上面的字典键。 为了查看数据而不会导致浏览器崩溃,我们可以将字典打印为一个字符串,并仅输出字符串的一些首字符。

from pprint import pformatdef print_dict(dictionary, num_chars=1000):print(pformat(dictionary)[:num_chars])print_dict(stops_dict['meta'])
'''
{'view': {'attribution': 'Berkeley Police Department','averageRating': 0,'category': 'Public Safety','columns': [{'dataTypeName': 'meta_data','fieldName': ':sid','flags': ['hidden'],'format': {},'id': -1,'name': 'sid','position': 0,'renderTypeName': 'meta_data'},{'dataTypeName': 'meta_data','fieldName': ':id','flags': ['hidden'],'format': {},'id': -1,'name': 'id','position': 0,'renderTypeName': 'meta_data'},{'dataTypeName': 'meta_data','fieldName': ':position','flags': ['hidden'],'format': {},
'''
print_dict(stops_dict['data'], num_chars=300)
'''
[[1,'29A1B912-A0A9-4431-ADC9-FB375809C32E',1,1444146408,'932858',1444146408,'932858',None,'2015-00004825','2015-01-26T00:10:00','SAN PABLO AVE / MARIN AVE','T','M',None,None],[2,'1644D161-1113-4C4F-BB2E-BF780E7AE73E',2,1444146408,'932858',14
'''

我们可以推断,字典中的'meta'键包含数据及其列的描述,'data'包含数据行的列表。 我们可以使用这些信息来初始化DataFrame

# Load the data from JSON and assign column titles
stops = pd.DataFrame(stops_dict['data'],columns=[c['name'] for c in stops_dict['meta']['view']['columns']])stops
sid id position created_at Incident Type Dispositions Location - Latitude Location - Longitude
0 1 29A1B912-A0A9-4431-ADC9-FB375809C32E 1 1444146408 T M None
1 2 1644D161-1113-4C4F-BB2E-BF780E7AE73E 2 1444146408 T M None
2 3 5338ABAB-1C96-488D-B55F-6A47AC505872 3 1444146408 T M None
29205 31079 C2B606ED-7872-4B0B-BC9B-4EF45149F34B 31079 1496269085 T BM2TWN; None
29206 31080 8FADF18D-7FE9-441D-8709-7BFEABDACA7A 31080 1496269085 T HM4TCS; 37.8698757000001
29207 31081 F60BD2A4-8C47-4BE7-B1C6-4934BE9DF838 31081 1496269085 1194 AR; 37.867207539

29208 行 × 15 列

# Prints column names
stops.columns
'''
Index(['sid', 'id', 'position', 'created_at', 'created_meta', 'updated_at','updated_meta', 'meta', 'Incident Number', 'Call Date/Time', 'Location','Incident Type', 'Dispositions', 'Location - Latitude','Location - Longitude'],dtype='object')
'''

该网站包含以下列的文档:

描述 类型
Incident 数字 计算机辅助调度(CAD)程序创建的事件数量 纯文本
Call Date/Time 事件/截停的日期和时间 日期时间
Location 事件/截停的一般位置 纯文本
Incident Type 这是在 CAD 程序中创建的发生事件的类型。 代码表示交通截停(T),可疑车辆截停(1196),行人截停(1194)和自行车截停(1194B)。 纯文本
Dispositions 按如下顺序组织:第一个字符为种族,如下所示:A(亚洲),B(黑人),H(西班牙裔),O(其他),W(白人);第二个字符为性别,如下所示:F(女性),M(男性);第三个字符为年龄范围,如下:1(小于 18),2(18-29),3(30-39),4(大于 40);第四个字符为原因,如下:I(调查),T(交通),R(合理怀疑),K(说教/假释),W(通缉);第五个字符为执行,如下:A(逮捕),C(引用),O(其他),W(警告);第六个字符为车辆搜索,如下:S(搜索),N(无搜索)。也可能出现其他处置,它们是:P - 主要案件报告,M - 仅 MDT,AR - 仅逮捕报告(未提交案件报告),IN - 事故报告,FC - 穿卡区,CO - 碰撞调查报告,MH - 紧急情况精神评估,TOW - 扣押车辆,0 或 00000 - 官员截停了超过五人。 纯文本
Location - Latitude 呼叫的一般纬度。 此数据仅在 2017 年 1 月之后上传。 数字
Location - Longitude 呼叫的一般经度。 此数据仅在 2017 年 1 月之后上传。 数字

请注意,网站不包含截停表的前 8 列的说明。 由于这些列似乎包含我们在此次分析中不感兴趣的元数据,因此我们从表中删除它们。

columns_to_drop = ['sid', 'id', 'position', 'created_at', 'created_meta','updated_at', 'updated_meta', 'meta']# This function takes in a DF and returns a DF so we can use it for .pipe
def drop_unneeded_cols(stops):return stops.drop(columns=columns_to_drop)stops.pipe(drop_unneeded_cols)
Incident Number Call Date/Time Location Incident Type Dispositions Location - Latitude Location - Longitude
0 2015-00004825 2015-01-26T00:10:00 SAN PABLO AVE / MARIN AVE T M None
1 2015-00004829 2015-01-26T00:50:00 SAN PABLO AVE / CHANNING WAY T M None
2 2015-00004831 2015-01-26T01:03:00 UNIVERSITY AVE / NINTH ST T M None
29205 2017-00024245 2017-04-30T22:59:26 UNIVERSITY AVE/6TH ST T BM2TWN; None
29206 2017-00024250 2017-04-30T23:19:27 UNIVERSITY AVE / WEST ST T HM4TCS; 37.8698757000001
29207 2017-00024254 2017-04-30T23:38:34 CHANNING WAY / BOWDITCH ST 1194 AR; 37.867207539

29208 行 × 7 列

与呼叫数据集一样,我们将回答截停数据集的以下三个问题:

  • 数据集中是否存在缺失值?
  • 是否有已填写的缺失值(例如 999 岁,未知年龄或上午 12:00 为未知日期)?
  • 数据的哪些部分是由人类输入的?

是否存在缺失值?

我们可以清楚地看到,有很多缺失的纬度和经度。 数据描述指出,这两列仅在 2017 年 1 月之后填写。

# True if row contains at least one null value
null_rows = stops.isnull().any(axis=1)stops[null_rows]
Incident Number Call Date/Time Location Incident Type Dispositions Location - Latitude Location - Longitude
0 2015-00004825 2015-01-26T00:10:00 SAN PABLO AVE / MARIN AVE T M None
1 2015-00004829 2015-01-26T00:50:00 SAN PABLO AVE / CHANNING WAY T M None
2 2015-00004831 2015-01-26T01:03:00 UNIVERSITY AVE / NINTH ST T M None
29078 2017-00023764 2017-04-29T01:59:36 2180 M L KING JR WAY 1194 BM4IWN; None
29180 2017-00024132 2017-04-30T12:54:23 6TH/UNI 1194 M; None
29205 2017-00024245 2017-04-30T22:59:26 UNIVERSITY AVE/6TH ST T BM2TWN; None

25067 行 × 7 列

我们可以检查其他列的缺失值:

# True if row contains at least one null value without checking
# the latitude and longitude columns
null_rows = stops.iloc[:, :-2].isnull().any(axis=1)df_interact(stops[null_rows])
# (63 rows, 7 columns) total

通过浏览上面的表格,我们可以看到所有其他缺失值在Dispositions列中。 不幸的是,我们从数据描述中并不知道,为什么这些值可能会缺失。 由于原始表格中,与 25,000 行相比,只有 63 个缺失值,因此我们可以继续进行分析,同时注意这些缺失值可能会影响结果。

有没有已填写的缺失值?

看起来,没有为我们填充之前的缺失值。 与呼叫数据集不同,它的日期和时间位于不同列中,截停数据集中的Call Date/Time列包含了日期和时间。

数据的哪些部分是由人类输入的?

与呼叫数据集一样,该数据集中的大部分列看起来都是由机器记录的,或者是人类选择的类别(例如事件类型)。

但是,Location列的输入值不一致。 果然,我们在数据中发现了一些输入错误:

stops['Location'].value_counts()
'''
2200 BLOCK SHATTUCK AVE            229
37.8693028530001~-122.272234021    213
UNIVERSITY AVE / SAN PABLO AVE     202...
VALLEY ST / DWIGHT WAY               1
COLLEGE AVE / SIXTY-THIRD ST         1
GRIZZLY PEAK BLVD / MARIN AVE        1
Name: Location, Length: 6393, dtype: int64
'''

真是一团糟! 有时看起来输入了地址,有时是十字路口,其他时候是经纬度。 不幸的是,我们没有非常完整的经纬度数据来代替这一列。 如果我们想将位置用于未来的分析,我们可能必须手动清理此列。

我们也可以检查Dispositions列:

dispositions = stops['Dispositions'].value_counts()# Outputs a slider to pan through the unique Dispositions in
# order of how often they appear
interact(lambda row=0: dispositions.iloc[row:row+7],row=(0, len(dispositions), 7))
# <function __main__.<lambda>>

Dispositions列也不一致。 例如,一些处置以空格开始,一些以分号结束,另一些包含多个条目。 值的多样性表明,该字段包含人类输入的值,应谨慎对待。

# Strange values...
dispositions.iloc[[0, 20, 30, 266, 1027]]
'''
M           1683
M;           238
 M           176
HF4TWN;       14
 OM4KWS        1
Name: Dispositions, dtype: int64
'''

另外,最常见的处置是M,它不是Dispositions列中允许的第一个字符。 这可能意味着,该列的格式会随时间而变化,或者允许官员输入处置,它不匹配数据描述中的格式。 无论如何,该列将很难处理。

我们可以采取一些简单的步骤来清理处置列,方法是删除前导和尾后空格,删除尾后分号并用逗号替换剩余的分号。

def clean_dispositions(stops):cleaned = (stops['Dispositions'].str.strip().str.rstrip(';').str.replace(';', ','))return stops.assign(Dispositions=cleaned)

和以前一样,我们现在可以使stops DF 由管道穿过我们定义的清理函数:

stops_final = (stops.pipe(drop_unneeded_cols).pipe(clean_dispositions))
df_interact(stops_final)
# (29208 rows, 7 columns) total

总结

这两个数据集表明,数据清理往往既困难又乏味。 清理 100% 的数据通常需要很长时间,但不清理数据会导致错误的结论;我们必须衡量我们的选择,并在每次遇到新数据集时达到平衡。

数据清理过程中做出的决定,会影响所有未来的分析。 例如,我们选择不清理截停数据集的Location列,因此我们应该谨慎对待该列。 在数据清理过程中做出的每一项决定,都应仔细记录以供日后参考,最好在笔记本上,以便代码和解释出现在一起。

# HIDDEN
# Save data to CSV for other chapters
# stops_final.to_csv('../ch5/data/stops.csv', index=False)

数据科学的原理与技巧 四、数据清理相关推荐

  1. 数据科学的原理与技巧 一、数据科学的生命周期

    一.数据科学的生命周期 原文:DS-100/textbook/notebooks/ch01 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 在数据科学中,我们使用大量不同的数据集 ...

  2. 数据科学的原理与技巧 五、探索性数据分析

    五.探索性数据分析 原文:DS-100/textbook/notebooks/ch05 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 探索性数据分析是一种态度,一种灵活的状态, ...

  3. 数据科学的原理与技巧 二、数据生成

    二.数据生成 原文:DS-100/textbook/notebooks/ch02 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 数据科学很难成为没有数据的科学. 因此重要的是, ...

  4. 数据科学的原理与技巧 三、处理表格数据

    三.处理表格数据 原文:DS-100/textbook/notebooks/ch03 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 索引.切片和排序 起步 在本章的每一节中,我 ...

  5. UCB DS100 讲义《数据科学的原理与技巧》校对活动正式启动 | ApacheCN

    贡献指南:https://github.com/apachecn/ds100-textbook-zh/blob/master/CONTRIBUTING.md 整体进度:https://github.c ...

  6. 【译Py】数据科学面试终极指南(四)

    欢迎来我的简书:呆鸟的译Py胡言 数据科学面试题 行为试题   与其它岗位的面试类似,数据科学面试也包括大量行为试题,面试官会考核求职者的软技能,了解求职者能否适应公司的企业文化. 请说明你以前做过的 ...

  7. python数据科学实践 常象宇_python数据科学

    Python语言拥有大量可用于存储.操作和洞察数据的程序库,已然成为深受数据科学研究人员推崇的工具.本书以IPython.NumPy.Pandas.Matplotlib和Scikit-Learn这5个 ...

  8. python数据科学实践 常象宇_Python数据科学实践

    章基于Python的数据科学环境搭建 1.1Python是数据科学"大势所趋" 1.2Anaconda入门--工欲善其事,必先利其器 1.3JupyterNotebook入门 1. ...

  9. python数据科学手册_小白入门Python数据科学

    前言 本文讲解了从零开始学习Python数据科学的全过程,涵盖各种工具和方法 你将会学习到如何使用python做基本的数据分析 你还可以了解机器学习算法的原理和使用 说明 先说一段题外话.我是一名数据 ...

最新文章

  1. 【Android 逆向】Android 逆向通用工具开发 ( Windows 平台静态库程序类型 | 编译逆向工具依赖的 Windows 平台静态库程序 )
  2. String, StringBuffer, StringBuilder(转载)
  3. 三创比赛关于软件设计的策划书_关于大学生创业和电商创业大赛
  4. cdi 2.7.5_看一下CDI 2.0 EDR1
  5. 双鉴探测器是哪两种探测方式结合_老师傅带你看懂火灾探测器的种类和基本原理,看完涨知识了...
  6. python excel 添加数据_使用pyexcel python在电子表格中添加行数据
  7. 虹膜归一化:仿射——图解仿射变换的旋转矩阵推导
  8. @程序员,不要再锤产品经理了,锤这个吧!!!
  9. 【Maven】Maven下载源码和Javadoc的方法
  10. 「leetcode」本周小结!(回溯算法系列二)
  11. sublimetext能编辑html语言,SublimeText_编辑保存的Html乱码问题解决
  12. aardio - 【库】虚表增强版
  13. Lab07 南向协议AC-DCN esight
  14. 33个神经网络训练技巧
  15. 电脑远程软件TeamViewer
  16. python打砖块游戏算法设计分析_基于pygame的打砖块游戏,做到一半,不带做了
  17. O2优化后,程序freez了(变量的读取过程被优化,使用volatile可解决)
  18. layui数据表格换行,错位
  19. May 18:PHP 函数
  20. [ 操作系统 ] 假定在单CPU条件下有下列要执行的作业,用一个执行时间图描述在采用非抢占优先级算法时执行这些作业的情况;对于上述算法,各个作业的周转时间是多少?平均周转时间是多少?

热门文章

  1. 【VS消除警告】VS消除特定警告/安全函数警告C4996 strncpy unsafe……
  2. 算法的浅论:算法前序
  3. 【华为OD机试真题 Java】找出通过车辆最多颜色 (A卷2022Q4)
  4. 进程管理API之find_get_pid
  5. 豆瓣超高评分《扫黑风暴》热评爬取可视化展示
  6. jQuery(一):概述、选择器、操作(元素本身、属性、内容、样式)、元素遍历、事件
  7. java练习04|银行利率表如下表所示,请计算存款10000元,活期1年、活期2年,定期1年,定期2年后的本息合计。
  8. Java实现利用在线的API对IP地址进行解析(内部代码分享)
  9. 《简化iOS APP上架流程,App Uploader助你搞定!》
  10. NAS折腾系列二:番外篇之瘦客户机+DoraOS实现远程办公