第一部分探索数据
提供在Python中清理数据所需的所有技能,从学习如何诊断问题数据到处理缺失值和异常值。
所以你刚刚得到了一个全新的数据集,并且渴望开始探索它。 但是你从哪里开始,你怎么能确定你的数据集是干净的? 本章将向您介绍Python中的数据清理世界! 您将学习如何探索数据,以便诊断异常值,缺失值和重复行等问题。
1、加载和查看数据
在本章中,将查看来自NYC Open Data门户的建筑署工作申请文件数据集的子集。该数据集包括2017年1月22日提交的工作申请。第一个任务是将此数据集加载到DataFrame中,然后使用.head()和.tail()方法对其进行检查。但是,很快发现打印结果不允许您查看所需的所有内容,因为列太多了。因此,您需要以另一种方式查看数据。
使用.shape和.columns属性可以查看DataFrame的形状并获取其列的列表。从这里,您可以看到哪些列与您想要询问的数据相关。为此,已预先加载了仅包含这些相关列的新DataFrame,df_subset。这是将在本章其余部分使用的DataFrame。
现在通过使用pandas探索数据集、熟悉数据集!最初的探索性分析是数据清理的关键第一步。
# Import pandas
import pandas as pd
# Read the file into a DataFrame: df
df = pd.read_csv('dob_job_application_filings_subset.csv')
#df2 = df[['Job #', 'Doc #', 'Borough', 'Initial Cost', 'Total Est. Fee','Existing Zoning Sqft', 'Proposed Zoning Sqft','Enlargement SQ Footage', 'Street Frontage', 'ExistingNo. of Stories','Proposed No. of Stories', 'Existing Height', 'Proposed Height']]
# Print the head of df
print(df.head())
# Print the tail of df
print(df.tail())
# Print the shape of df
print(df.shape)
# Print the columns of df
print(df.columns)
# Print the head and tail of df_subset
print(df_subset.head())
print(df_subset.tail())
#print(df2.equals(df_subset))
如下为df_subset的DataFrame, [12846 rows x 13 columns]
'Job #', 'Doc #', 'Borough', 'Initial Cost', 'Total Est. Fee','Existing Zoning Sqft', 'Proposed Zoning Sqft','Enlargement SQ Footage', 'Street Frontage', 'ExistingNo. of Stories','Proposed No. of Stories', 'Existing Height', 'Proposed Height'
Initial Cost Total Est. Fee
0 $75000.00 $986.00
1 $0.00 $1144.00
2 $30000.00 $522.50
3 $1500.00 $225.00
4 $19500.00 $389.50
Job # ... Proposed Height
0 121577873 ... 0
1 520129502 ... 0
2 121601560 ... 54
3 121601203 ... 120
4 121601338 ... 64
5 121589753 ... 1250
6 320738001 ... 36
7 121601374 ... 300
8 121583054 ... 398
9 121601392 ... 16
10 121601169 ... 60
11 420828206 ... 28
12 420828493 ... 15
13 440075142 ... 12
14 420836723 ... 15
15 121584525 ... 0
16 121600311 ... 75
17 320744290 ... 52
18 320744557 ... 24
19 320744370 ... 25
20 220286232 ... 101
21 121599779 ... 520
22 420837401 ... 26
23 220286287 ... 60
24 240024362 ... 180
25 320744316 ... 33
26 121601249 ... 0
27 121592605 ... 546
28 121577873 ... 200
29 520129496 ... 15
... ... ... ...
12816 121675188 ... 210
12817 520143942 ... 15
12818 320833586 ... 74
12819 220307406 ... 0
12820 121641312 ... 60
12821 140087322 ... 165
12822 121680859 ... 160
12823 121678167 ... 99
12824 320721206 ... 0
12825 121681019 ... 42
12826 520143862 ... 37
12827 520143933 ... 29
12828 320723160 ... 29
12829 420857022 ... 30
12830 420857077 ... 30
12831 220307433 ... 20
12832 320590552 ... 39
12833 420606865 ... 0
12834 121681135 ... 30
12835 121632331 ... 170
12836 121681108 ... 190
12837 121330087 ... 0
12838 140087420 ... 150
12839 440089538 ... 45
12840 420606865 ... 0
12841 520143988 ... 10
12842 121613833 ... 55
12843 121681260 ... 64
12844 320771704 ... 18
12845 520143951 ... 18
可以看出有很多0值,而且Initial Cost和 Total Est. Fee的数值前面都有个‘$’
2、进一步诊断
在上一步中,确定了一些可能不干净或缺失的数据。 现在,继续使用非常有用的.info()方法诊断数据。
.info()方法提供有关DataFrame的重要信息,例如行数,列数,每列中的非缺失值数以及每列中存储的数据类型。
这种信息可以让您确认“ 'Initial Cost'
”和“'Total Est. Fee'”。 费用列是数字或字符串。 从结果中,您还可以查看所有列是否都包含完整数据。
# Print the info of df
print(df.info())
# Print the info of df_subset
print(df_subset.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 12846 entries, 0 to 12845
Data columns (total 82 columns):
Job # 12846 non-null int64
Doc # 12846 non-null int64
Borough 12846 non-null object
House # 12846 non-null object
Street Name 12846 non-null object
Block 12846 non-null int64
Lot 12846 non-null int64
Bin # 12846 non-null int64
Job Type 12846 non-null object
Job Status 12846 non-null object
Job Status Descrp 12846 non-null object
Latest Action Date 12846 non-null object
Building Type 12846 non-null object
Community - Board 12846 non-null object
Cluster 0 non-null float64
Landmarked 2067 non-null object
Adult Estab 1 non-null object
Loft Board 65 non-null object
City Owned 1419 non-null object
Little e 365 non-null object
PC Filed 0 non-null float64
eFiling Filed 12846 non-null object
Plumbing 12846 non-null object
Mechanical 12846 non-null object
Boiler 12846 non-null object
Fuel Burning 12846 non-null object
Fuel Storage 12846 non-null object
Standpipe 12846 non-null object
Sprinkler 12846 non-null object
Fire Alarm 12846 non-null object
Equipment 12846 non-null object
Fire Suppression 12846 non-null object
Curb Cut 12846 non-null object
Other 12846 non-null object
Other Description 12846 non-null object
Applicant's First Name 12846 non-null object
Applicant's Last Name 12846 non-null object
Applicant Professional Title 12846 non-null object
Applicant License # 12846 non-null object
Professional Cert 6908 non-null object
Pre- Filing Date 12846 non-null object
Paid 11961 non-null object
Fully Paid 11963 non-null object
Assigned 3817 non-null object
Approved 4062 non-null object
Fully Permitted 1495 non-null object
Initial Cost 12846 non-null object
Total Est. Fee 12846 non-null object
Fee Status 12846 non-null object
Existing Zoning Sqft 12846 non-null int64
Proposed Zoning Sqft 12846 non-null int64
Horizontal Enlrgmt 231 non-null object
Vertical Enlrgmt 142 non-null object
Enlargement SQ Footage 12846 non-null int64
Street Frontage 12846 non-null int64
ExistingNo. of Stories 12846 non-null int64
Proposed No. of Stories 12846 non-null int64
Existing Height 12846 non-null int64
Proposed Height 12846 non-null int64
Existing Dwelling Units 12846 non-null object
Proposed Dwelling Units 12846 non-null object
Existing Occupancy 12846 non-null object
Proposed Occupancy 12846 non-null object
Site Fill 8641 non-null object
Zoning Dist1 11263 non-null object
Zoning Dist2 1652 non-null object
Zoning Dist3 88 non-null object
Special District 1 3062 non-null object
Special District 2 848 non-null object
Owner Type 0 non-null float64
Non-Profit 971 non-null object
Owner's First Name 12846 non-null object
Owner's Last Name 12846 non-null object
Owner's Business Name 12846 non-null object
Owner's House Number 12846 non-null object
Owner'sHouse Street Name 12846 non-null object
City 12846 non-null object
State 12846 non-null object
Zip 12846 non-null int64
Owner'sPhone # 12846 non-null int64
Job Description 12699 non-null object
DOBRunDate 12846 non-null object
dtypes: float64(3), int64(15), object(64)
memory usage: 8.0+ MB
None
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 12846 entries, 0 to 12845
Data columns (total 13 columns):
Job # 12846 non-null int64
Doc # 12846 non-null int64
Borough 12846 non-null object
Initial Cost 12846 non-null object
Total Est. Fee 12846 non-null object
Existing Zoning Sqft 12846 non-null int64
Proposed Zoning Sqft 12846 non-null int64
Enlargement SQ Footage 12846 non-null int64
Street Frontage 12846 non-null int64
ExistingNo. of Stories 12846 non-null int64
Proposed No. of Stories 12846 non-null int64
Existing Height 12846 non-null int64
Proposed Height 12846 non-null int64
dtypes: int64(10), object(3)
memory usage: 1.3+ MB
None
注意到列“Initial Cost”和“Total Est. Fee ”。 其类型是对象。因此, 需要删除这些列中每个值开头的货币符号,并且需要将列值转换为数字。 在完整的DataFrame中,注意有很多缺失值。 在上一个练习中看到,还有很多0值。 鉴于完整数据集中缺少的数据量,这些0值很可能代表缺失的数据。
假设已经将df.subset 中的列“Initial Cost”和“Total Est. Fee ”数值化了。
In [1]: df.describe()
Out[1]:
Job # ... Proposed Height
count 1.284600e+04 ... 12846.000000
mean 2.426788e+08 ... 94.917562
std 1.312507e+08 ... 146.580666
min 1.036438e+08 ... 0.000000
25% 1.216206e+08 ... 21.000000
50% 2.202645e+08 ... 45.000000
75% 3.208652e+08 ... 107.000000
max 5.400246e+08 ... 4200.000000
[8 rows x 12 columns],本来有13列,df.describe()后少了一列。
3、分类数据的频率计数
如您所见,.describe()只能用于数字列。 那么,当您有其他数据时,如何诊断数据问题?
一种方法是使用.value_counts()方法,该方法返回列中每个唯一值的频率计数!
此方法还有一个名为dropna的可选参数,默认情况下为True。 这意味着如果您在列中缺少数据,它将不会给出它们的频率计数。 您希望将dropna列设置为False,因此如果列中缺少值,它将为您提供频率计数。
# Print the value counts for 'Borough'
print(df['Borough'].value_counts(dropna=False))
# Print the value_counts for 'State'
print(df.State.value_counts(dropna=False))
# Print the value counts for 'Site Fill'
print(df['Site Fill'].value_counts(dropna=False))
MANHATTAN 6310
BROOKLYN 2866
QUEENS 2121
BRONX 974
STATEN ISLAND 575
Name: Borough, dtype: int64
NY 12391
NJ 241
PA 38
CA 20
OH 19
FL 17
IL 17
CT 16
TX 13
TN 10
MD 7
DC 7
MA 6
GA 6
KS 6
VA 5
CO 4
WI 3
SC 3
AZ 3
MN 3
UT 2
RI 2
NC 2
WA 1
NM 1
IN 1
VT 1
MI 1
Name: State, dtype: int64
NOT APPLICABLE 7806
NaN 4205
ON-SITE 519
OFF-SITE 186
USE UNDER 300 CU.YD 130
Name: Site Fill, dtype: int64
请注意“State”列中的所有值不都是NY。 这是一个有趣的发现,因为这些数据应该包含在纽约市提交的申请。
奇怪的是,所有'Borough'值都是正确的。关于为什么会出现这种情况的一个良好开端是找到并查看该数据集的代码簿。
此外,对于“Site Fill”列,可能需要或可能不需要在最终分析中将NOT APPLICABLE值重新编码为NaN。
4、视觉探索性数据分析---发现异常值和明显错误的好方法
到目前为止,一直在查看数据的描述性统计信息。 确认数字的最佳方法之一是绘制和可视化数据。
4.1使用直方图可视化单个变量
首先,使用直方图可视化单个变量的数值。 在本练习中处理的列是“Existing Zoning Sqft”。
# Import matplotlib.pyplot
import matplotlib.pyplot as plt
# Describe the column
df['Existing Zoning Sqft'].describe()
# Plot the histogram
df['Existing Zoning Sqft'].plot(kind='hist', rot=70, logx=True, logy=True)
# Display the histogram
plt.show()
count 1.284600e+04
mean 1.439973e+03
std 3.860757e+04
min 0.000000e+00
25% 0.000000e+00
50% 0.000000e+00
75% 0.000000e+0
max 2.873107e+06
Name: Existing Zoning Sqft, dtype: float64
可以看出‘Existing Zoning Sqft’列,最大值和最小值相差很大
虽然可视化数据是理解它的好方法,但请记住,没有一种技术比另一种更好。 正如在此处所看到的,仍需要查看摘要统计信息,以便更好地了解数据。你期望在图的左侧有大量的计数,因为第25,第50和第75百分位数的值为0.该图显示我们几乎没有任何计数接近最大值,表示为异常值。
4.2 使用箱线图可视化多个变量
直方图是可视化单个变量的好方法。 为了可视化多个变量,箱线图很有用,尤其是当其中一个变量是分类变量时。
# Import necessary modules
import pandas as pd
import matplotlib.pyplot as plt
# Create the boxplot
df.boxplot(column='initial_cost', by='Borough', rot=90)
# Display the plot
plt.show()
你可以看到2个极端异常值在曼哈顿区。 最初的猜测可能是,由于曼哈顿的土地非常昂贵,这些异常值可能是有效的数据点。 同样,需要进一步调查以确定是否可以删除或保留数据中的这些点。
4.3 使用散点图可视化多个变量
箱线图适合在不同类别之间进行比较的数字列(一列为数字,一列不是数字)。 当您想要显示两个数字列时,散点图是理想的。
# Import necessary modules
import pandas as pd
import matplotlib.pyplot as plt
# Create and display the first scatter plot
df.plot(kind='scatter', x='initial_cost', y='total_est_fee', rot=70)
plt.show()
# Create and display the second scatter plot
df_subset.plot(kind='scatter', x='initial_cost', y='total_est_fee', rot=70)
plt.show()
从第二个图来看,似乎'initial_cost'和'total_est_fee'之间存在很强的相关性。 另外,请注意“initial_cost”为0的大量点。很难从第一个图中推断出任何趋势,因为它由异常值控制。