美文网首页UCB DS100 数据科学的原理与技巧程序员
数据科学的原理与技巧 五、探索性数据分析

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

作者: 布客飞龙 | 来源:发表于2018-06-07 22:39 被阅读28次

    五、探索性数据分析

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

    译者:飞龙

    协议:CC BY-NC-SA 4.0

    自豪地采用谷歌翻译

    探索性数据分析是一种态度,一种灵活的状态,一种寻找那些我们认为不存在和存在的东西的心愿。

    John Tukey

    在探索性数据分析(EDA),也就是数据科学生命周期的第三步中,我们总结,展示和转换数据,以便更深入地理解它。 特别是,通过 EDA,我们发现数据中的潜在问题,并发现可用于进一步分析的趋势。

    我们试图了解我们数据的以下属性:

    结构:我们数据文件的格式。
    粒度:每行和每列的精细程度。
    范围:我们的数据有多么完整或不完整。
    时间性:数据是不是当时的情况。
    忠实度:数据捕捉“现实”有多好。

    尽管我们分别介绍了数据清理和 EDA 来有助于组织本书,但在实践中,你经常会在两者之间切换。 例如,列的可视化可能会向你展示,应使用数据清理技术进行处理的格式错误的值。 考虑到这一点,我们回顾伯克利警察局的数据集来进行探索。

    结构和连接

    结构

    数据集的结构指的是数据文件的“形状”。 基本上,这指的是输入数据的格式。例如,我们看到呼叫数据集是 CSV(逗号分隔值)文件:

    !head data/Berkeley_PD_-_Calls_for_Service.csv
    
    CASENO,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
    

    另一方面,截停数据集是 JSON(JavaScript 对象表示法)文件。

    # Show first and last 5 lines of file
    !head -n 5 data/stops.json
    !echo '...'
    !tail -n 5 data/stops.json
    
    {
      "meta" : {
        "view" : {
          "id" : "6e9j-pj9p",
          "name" : "Berkeley PD - Stop Data",
    ...
    , [ 31079, "C2B606ED-7872-4B0B-BC9B-4EF45149F34B", 31079, 1496269085, "932858", 1496269085, "932858", null, "2017-00024245", "2017-04-30T22:59:26", " UNIVERSITY AVE/6TH ST", "T", "BM2TWN; ", null, null ]
    , [ 31080, "8FADF18D-7FE9-441D-8709-7BFEABDACA7A", 31080, 1496269085, "932858", 1496269085, "932858", null, "2017-00024250", "2017-04-30T23:19:27", " UNIVERSITY AVE /  WEST ST", "T", "HM4TCS; ", "37.8698757000001", "-122.286550846" ]
    , [ 31081, "F60BD2A4-8C47-4BE7-B1C6-4934BE9DF838", 31081, 1496269085, "932858", 1496269085, "932858", null, "2017-00024254", "2017-04-30T23:38:34", " CHANNING WAY /  BOWDITCH ST", "1194", "AR; ", "37.867207539", "-122.256529377" ]
     ]
    }
    

    当然,还有很多其他类型的数据格式。 以下是最常见格式的列表:

    • 逗号分隔值(CSV)和制表符分隔值(TSV)。 这些文件包含由逗号(CSV)或制表符(\t,TSV)分隔的表格数据。 这些文件通常很容易处理,因为数据的输入格式与DataFrame类似。

    • JavaScript 对象表示法(JSON)。 这些文件包含嵌套字典格式的数据。 通常我们必须将整个文件读为 Python 字典,然后弄清楚如何从字典中为DataFrame提取字段。

    • 可扩展标记语言(XML)或超文本标记语言(HTML)。 这些文件也包含嵌套格式的数据,例如:

      <?xml version="1.0" encoding="UTF-8"?>
      <note>
          <to>Tove</to>
          <from>Jani</from>
          <heading>Reminder</heading>
          <body>Don't forget me this weekend!</body>
      </note>
      

      在后面的章节中,我们将使用 XPath 从这些类型的文件中提取数据。

    • 日志数据。许多应用在运行时会以非结构化文本格式输出一些数据,例如:

      2005-03-23 23:47:11,663 - sa - INFO - creating an instance of aux_module.Aux
      2005-03-23 23:47:11,665 - sa.aux.Aux - INFO - creating an instance of Aux
      2005-03-23 23:47:11,665 - sa - INFO - created an instance of aux_module.Aux
      2005-03-23 23:47:11,668 - sa - INFO - calling aux_module.Aux.do_something
      2005-03-23 23:47:11,668 - sa.aux.Aux - INFO - doing something
      

      在后面的章节中,我们将使用正则表达式从这些类型的文件中提取数据。

    连接(Join)

    数据通常会分成多个表格。 例如,一张表可能描述一些人的个人信息,而另一张表可能包含他们的电子邮件:

    personal information while another will contain their emails:
    
    people = pd.DataFrame(
        [["Joey",      "blue",    42,  "M"],
         ["Weiwei",    "blue",    50,  "F"],
         ["Joey",      "green",    8,  "M"],
         ["Karina",    "green",    7,  "F"],
         ["Nhi",       "blue",     3,  "F"],
         ["Sam",       "pink",   -42,  "M"]], 
        columns = ["Name", "Color", "Number", "Sex"])
    
    people
    
    Name Color Number Sex
    0 Joey blue 42 M
    1 Weiwei blue 50 F
    2 Joey green 8 M
    3 Karina green 7 F
    4 Fernando pink -9 M
    5 Nhi blue 3 F
    6 Sam pink -42 M
    email = pd.DataFrame(
        [["Deb",  "deborah_nolan@berkeley.edu"],
         ["Sam",  "samlau95@berkeley.edu"],
         ["John", "doe@nope.com"],
         ["Joey", "jegonzal@cs.berkeley.edu"],
         ["Weiwei", "weiwzhang@berkeley.edu"],
         ["Weiwei", "weiwzhang+123@berkeley.edu"],
         ["Karina", "kgoot@berkeley.edu"]], 
        columns = ["User Name", "Email"])
    
    email
    
    User Name Email
    0 Deb deborah_nolan@berkeley.edu
    1 Sam samlau95@berkeley.edu
    2 John doe@nope.com
    3 Joey jegonzal@cs.berkeley.edu
    4 Weiwei weiwzhang@berkeley.edu
    5 Weiwei weiwzhang+123@berkeley.edu
    6 Karina kgoot@berkeley.edu

    为了使每个人匹配他或她的电子邮件,我们可以在包含用户名的列上连接两个表。 然后,我们必须决定,如何处理出现在一张表上而没有在另一张表上的人。 例如,Fernando出现在people表中,但不出现在email表中。 我们有几种类型的连接,用于每个匹配缺失值的策略。 最常见的连接之一是内连接,其中任何不匹配的行都不放入最终结果中:

    # Fernando, Nhi, Deb, and John don't appear
    people.merge(email, how='inner', left_on='Name', right_on='User Name')
    
    Name Color Number Sex User Name Email
    0 Joey blue 42 M Joey jegonzal@cs.berkeley.edu
    1 Joey green 8 M Joey jegonzal@cs.berkeley.edu
    2 Weiwei blue 50 F Weiwei weiwzhang@berkeley.edu
    3 Weiwei blue 50 F Weiwei weiwzhang+123@berkeley.edu
    4 Karina green 7 F Karina kgoot@berkeley.edu
    5 Sam pink -42 M Sam samlau95@berkeley.edu

    这是我们经常使用的四个基本连接:内连接,全连接(有时称为“外连接”),左连接和右连接。 以下是个图表,展示了这些类型的连接之间的区别。

    image

    运行下面的代码,并使用生成的下拉菜单,来展示peopleemail表格的四种不同的连接的结果。 注意对于外,左和右连接,哪些行包含了NaN值。

    # HIDDEN
    def join_demo(join_type):
        display(HTML('people and email tables:'))
        display_two(people, email)
        display(HTML('<br>'))
        display(HTML('Joined table:'))
        display(people.merge(email, how=join_type,
                             left_on='Name', right_on='User Name'))
        
    interact(join_demo, join_type=['inner', 'outer', 'left', 'right']);
    

    结构检查清单

    查看数据集的结构之后,你应该回答以下问题。我们将根据呼叫和截停数据集回答它们。

    数据是标准格式还是编码过的?

    标准格式包括:

    表格数据:CSV,TSV,Excel,SQL
    嵌套数据:JSON,XML

    呼叫数据集采用 CSV 格式,而截停数据集采用 JSON 格式。

    数据是组织为记录形式(例如行)的吗?如果不是,我们可以通过解析数据来定义记录吗?

    呼叫数据集按行出现;我们从截停数据集中提取记录。

    数据是否嵌套?如果是这样,我们是否可以适当地提取非嵌套的数据?

    呼叫数据集不是嵌套的;我们不必过于费力从截停数据集中获取非嵌套的数据。

    数据是否引用了其他数据?如果是这样,我们可以连接数据吗?

    呼叫数据集引用了星期表。连接这两张表让我们知道数据集中每个事件的星期。截取数据集没有明显的引用。

    每个记录中的字段(例如,列)是什么?每列的类型是什么?

    呼叫和截停数据集的字段,在每个数据集的“数据清理”一节中介绍。

    粒度

    数据的粒度是数据中每条记录代表什么。 例如,在呼叫数据集中,每条记录代表一次警务呼叫。

    # HIDDEN
    calls = pd.read_csv('data/calls.csv')
    calls.head()
    
    CASENO OFFENSE CVLEGEND BLKADDR EVENTDTTM Latitude Longitude Day
    0 17091420 BURGLARY AUTO BURGLARY - VEHICLE 2500 LE CONTE AVE 2017-07-23 06:00:00 37.876965 -122.260544 Sunday
    1 17038302 BURGLARY AUTO BURGLARY - VEHICLE BOWDITCH STREET & CHANNING WAY 2017-07-02 22:00:00 37.867209 -122.256554 Sunday
    2 17049346 THEFT MISD. (UNDER $950) LARCENY 2900 CHANNING WAY 2017-08-20 23:20:00 37.867948 -122.250664 Sunday
    3 17091319 THEFT MISD. (UNDER $950) LARCENY 2100 RUSSELL ST 2017-07-09 04:15:00 37.856719 -122.266672 Sunday
    4 17044238 DISTURBANCE DISORDERLY CONDUCT TELEGRAPH AVENUE & DURANT AVE 2017-07-30 01:16:00 37.867816 -122.258994 Sunday

    在截停数据集中,每条记录代表一次警务截停事件。

    # HIDDEN
    stops = pd.read_csv('data/stops.csv', parse_dates=[1], infer_datetime_format=True)
    stops.head()
    
    Incident Number Call Date/Time Location Incident Type Dispositions Location - Latitude Location - Longitude
    0 2015-00004825 2015-01-26 00:10:00 SAN PABLO AVE / MARIN AVE T M NaN NaN
    1 2015-00004829 2015-01-26 00:50:00 SAN PABLO AVE / CHANNING WAY T M NaN NaN
    2 2015-00004831 2015-01-26 01:03:00 UNIVERSITY AVE / NINTH ST T M NaN NaN
    3 2015-00004848 2015-01-26 07:16:00 2000 BLOCK BERKELEY WAY 1194 BM4ICN NaN NaN
    4 2015-00004849 2015-01-26 07:43:00 1700 BLOCK SAN PABLO AVE 1194 BM4ICN NaN NaN

    另一方面,我们可能以下列格式收到接受数据:

    # HIDDEN
    (stops
     .groupby(stops['Call Date/Time'].dt.date)
     .size()
     .rename('Num Incidents')
     .to_frame()
    )
    
    Num Incidents
    Call Date/Time
    2015-01-26 46
    2015-01-27 57
    2015-01-28 56
    ... ...
    2017-04-28 82
    2017-04-29 86
    2017-04-30 59

    825 rows × 1 columns

    在这种情况下,表格中的每个记录对应于单个日期而不是单个事件。 我们会将此表描述为,它具有比上述更粗的粒度。 了解数据的粒度非常重要,因为它决定了你可以执行哪种分析。 一般来说,细粒度由于粗粒度;虽然我们可以使用分组和旋转将细粒度变为粗粒度,但我们没有几个工具可以由粗到精。

    粒度检查清单

    查看数据集的粒度后,你应该回答以下问题。我们将根据呼叫和截停数据集回答他们。

    一条记录代表了什么?

    在呼叫数据集中,每条记录代表一次警务呼叫。在截停数据集中,每条记录代表一次警务截停事件。

    所有记录的粒度是否在同一级别? (有时一个表格将包含汇总行。)

    是的,对于呼叫和截停数据集是如此。

    如果数据是聚合的,聚合是如何进行的?采样和平均是常见的聚合。

    就有印象记住,在两个数据集中,位置都是输入为街区,而不是特定的地址。

    我们可以对数据执行什么类型的聚合?

    例如,随着时间的推移,将个体聚合为人口统计分组,或个体事件聚合为总数。

    在这种情况下,我们可以聚合为不同的日期或时间粒度。例如,我们可以使用聚合,找到事件最常见的一天的某个小时。我们也可能能够按照事件地点聚合,来发现事件最多的伯克利地区。

    范围

    数据集的范围是指数据集的覆盖面,与我们有兴趣分析的东西相关。我们试图回答我们数据范围的以下问题:

    数据是否涵盖了感兴趣的话题?

    例如,呼叫和截停数据集包含在伯克利发生的呼叫和截停事件。然而,如果我们对加利福尼亚州的犯罪事件感兴趣,那么这些数据集的范围将会过于有限。

    一般来说,较大的范围比较小的范围更有用,因为我们可以将较大的范围过滤为较小的范围,但通常不能从较小的范围转到较大的范围。例如,如果我们有美国的警务截停数据集,我们可以取数据集的子集,来调查伯克利。

    请记住,范围是一个广义术语,并不总是用于描述地理位置。例如,它也可以指时间覆盖面 - 呼叫数据集仅包含 180 天的数据。

    在调查数据生成的过程中,我们经常会处理数据集的范围,并在 EDA 期间确认数据集的范围。让我们来确认呼叫数据集的地理和时间范围。

    calls
    
    CASENO OFFENSE CVLEGEND BLKADDR EVENTDTTM Latitude Longitude Day
    0 17091420 BURGLARY AUTO BURGLARY - VEHICLE 2500 LE CONTE AVE 2017-07-23 06:00:00 37.876965 -122.260544 Sunday
    1 17038302 BURGLARY AUTO BURGLARY - VEHICLE BOWDITCH STREET & CHANNING WAY 2017-07-02 22:00:00 37.867209 -122.256554 Sunday
    2 17049346 THEFT MISD. (UNDER $950) LARCENY 2900 CHANNING WAY 2017-08-20 23:20:00 37.867948 -122.250664 Sunday
    ... ... ... ... ... ... ... ... ...
    5505 17021604 IDENTITY THEFT FRAUD 100 MONTROSE RD 2017-03-31 00:00:00 37.896218 -122.270671 Friday
    5506 17033201 DISTURBANCE DISORDERLY CONDUCT 2300 COLLEGE AVE 2017-06-09 22:34:00 37.868957 -122.254552 Friday
    5507 17047247 BURGLARY AUTO BURGLARY - VEHICLE UNIVERSITY AVENUE & CHESTNUT ST 2017-08-11 20:00:00 37.869679 -122.288038 Friday

    5508 rows × 8 columns

    # Shows earliest and latest dates in calls
    calls['EVENTDTTM'].dt.date.sort_values()
    '''
    1384    2017-03-02
    1264    2017-03-02
    1408    2017-03-02
               ...    
    3516    2017-08-28
    3409    2017-08-28
    3631    2017-08-28
    Name: EVENTDTTM, Length: 5508, dtype: object
    '''
    
    calls['EVENTDTTM'].dt.date.max() - calls['EVENTDTTM'].dt.date.min()
    # datetime.timedelta(179)
    

    该表格包含 179 天的时间段的数据,该时间段足够接近数据描述中的 180 天,我们可以假设 2017 年 4 月 14 日或 2017 年 8 月 29 日没有呼叫。

    为了检查地理范围,我们可以使用地图:

    import folium # Use the Folium Javascript Map Library
    import folium.plugins
    
    SF_COORDINATES = (37.87, -122.28)
    sf_map = folium.Map(location=SF_COORDINATES, zoom_start=13)
    locs = calls[['Latitude', 'Longitude']].astype('float').dropna().as_matrix()
    heatmap = folium.plugins.HeatMap(locs.tolist(), radius = 10)
    sf_map.add_child(heatmap)
    
    image

    除少数例外情况外,呼叫数据集覆盖了伯克利地区。 我们可以看到,大多数警务呼叫发生在伯克利市中心和 UCB 校区的南部。

    现在我们来确认截停数据集的时间和地理范围:

    stops
    
    Incident Number Call Date/Time Location Incident Type Dispositions Location - Latitude Location - Longitude
    0 2015-00004825 2015-01-26 00:10:00 SAN PABLO AVE / MARIN AVE T M NaN NaN
    1 2015-00004829 2015-01-26 00:50:00 SAN PABLO AVE / CHANNING WAY T M NaN NaN
    2 2015-00004831 2015-01-26 01:03:00 UNIVERSITY AVE / NINTH ST T M NaN NaN
    ... ... ... ... ... ... ... ...
    29205 2017-00024245 2017-04-30 22:59:26 UNIVERSITY AVE/6TH ST T BM2TWN NaN NaN
    29206 2017-00024250 2017-04-30 23:19:27 UNIVERSITY AVE / WEST ST T HM4TCS 37.869876 -122.286551
    29207 2017-00024254 2017-04-30 23:38:34 CHANNING WAY / BOWDITCH ST 1194 AR 37.867208 -122.256529

    29208 rows × 7 columns

    stops['Call Date/Time'].dt.date.sort_values()
    '''
    0        2015-01-26
    25       2015-01-26
    26       2015-01-26
                ...    
    29175    2017-04-30
    29177    2017-04-30
    29207    2017-04-30
    Name: Call Date/Time, Length: 29208, dtype: object
    '''
    

    如承诺的那样,数据收集工作从 2015 年 1 月 26 日开始。因为它在 2017 年 4 月 30 日起停止,数据似乎在 2017 年 5 月初左右下载。让我们绘制地图来查看地理数据:

    SF_COORDINATES = (37.87, -122.28)
    sf_map = folium.Map(location=SF_COORDINATES, zoom_start=13)
    locs = stops[['Location - Latitude', 'Location - Longitude']].astype('float').dropna().as_matrix()
    heatmap = folium.plugins.HeatMap(locs.tolist(), radius = 10)
    sf_map.add_child(heatmap)
    
    image

    我们可以证实,数据集中在伯克利发生的警务截停,以及大多数警务呼叫,都发生在伯克利市中心和伯克利西部地区。

    时间性

    时间性是指数据在时间上如何表示,特别是数据集中的日期和时间字段。我们试图通过这些字段来了解以下特征:

    数据集中日期和时间字段的含义是什么?

    在呼叫和截停数据集中,日期时间字段表示警务呼叫或截停的时间。然而,截停数据集最初还有一个日期时间字段,记录案件什么时候输入到数据库,我们在数据清理过程中将其移除,因为我们认为它不适用于分析。

    另外,我们应该注意日期时间字段的时区和夏令时,特别是在处理来自多个位置的数据的时候。

    日期和时间字段在数据中有什么表示形式?

    虽然美国使用MM/DD/YYYY格式,但许多其他国家使用DD/MM/YYYY格式。仍有更多格式在世界各地使用,分析数据时认识到这些差异非常重要。

    在呼叫和截停数据集中,日期显示为MM/DD/YYYY格式。

    是否有奇怪的时间戳,它可能代表空值?

    某些程序使用占位符而不是空值。例如,Excel 的默认日期是 1990 年 1 月 1 日,而 Mac 上的 Excel 则是 1904 年 1 月 1 日。许多应用将生成 1970 年 1 月 1 日 12:00 或 1969 年 12 月 31 日 11:59 pm 的默认日期时间,因为这是用于时间戳的 Unix 纪元。如果你在数据中注意到这些时间戳的多个实例,则应该谨慎并仔细检查数据源。 呼叫或截停数据集都不包含任何这些可疑值。

    忠实度

    如果我们相信它能准确捕捉现实,我们将数据集描述为“忠实的”。通常,不可信的数据集包含:

    不切实际或不正确的值

    例如,未来的日期,不存在的位置,负数或较大离群值。

    明显违反的依赖关系

    例如,个人的年龄和生日不匹配。

    手动输入的数据

    我们看到,这些通常充满了拼写错误和不一致。

    明显的数据伪造迹象

    例如,重复的名称,伪造的电子邮件地址,或重复使用不常见的名称或字段。

    注意与数据清理的许多相似之处。 我们提到,我们经常在数据清理和 EDA 之间来回切换,特别是在确定数据忠实度的时候。 例如,可视化经常帮助我们识别数据中的奇怪条目。

    calls = pd.read_csv('data/calls.csv')
    calls.head()
    
    CASENO OFFENSE EVENTDT EVENTTM ... BLKADDR Latitude Longitude Day
    0 17091420 BURGLARY AUTO 07/23/2017 12:00:00 AM 06:00 ... 2500 LE CONTE AVE 37.876965 -122.260544 Sunday
    1 17038302 BURGLARY AUTO 07/02/2017 12:00:00 AM 22:00 ... BOWDITCH STREET & CHANNING WAY 37.867209 -122.256554 Sunday
    2 17049346 THEFT MISD. (UNDER $950) 08/20/2017 12:00:00 AM 23:20 ... 2900 CHANNING WAY 37.867948 -122.250664 Sunday
    3 17091319 THEFT MISD. (UNDER $950) 07/09/2017 12:00:00 AM 04:15 ... 2100 RUSSELL ST 37.856719 -122.266672 Sunday
    4 17044238 DISTURBANCE 07/30/2017 12:00:00 AM 01:16 ... TELEGRAPH AVENUE & DURANT AVE 37.867816 -122.258994 Sunday

    5 rows × 9 columns

    calls['CASENO'].plot.hist(bins=30)
    # <matplotlib.axes._subplots.AxesSubplot at 0x1a1ebb2898>
    
    image

    请注意1703000017090000处的非预期的簇。通过绘制案例编号的分布,我们可以很快查看数据中的异常。 在这种情况下,我们可能会猜测,两个不同的警察团队为他们的呼叫使用不同的案件编号。

    数据探索通常会发现异常情况;如果可以修复,我们可以使用数据清理技术。

    相关文章

      网友评论

        本文标题:数据科学的原理与技巧 五、探索性数据分析

        本文链接:https://www.haomeiwen.com/subject/inwnsftx.html