构建你的数据科学作品集：用数据讲故事
这是如何建立 数据科学作品集 Data Science Portfolio 系列文章中的第一篇。如果你喜欢这篇文章并且想知道此系列的下一篇文章何时发表，你可以在页面底部订阅。数据科学公司们在决定雇佣一个人时越来越看重其作品集。其中一个原因就是 作品集 portfolio 是分析一个人真实技能的最好方式。好消息是，作品集是完全可以被你掌控的。如果你在其上投入了一些工作，你就能够做出一个令那些公司印象深刻的作品集结果。建立一个高质量作品集的第一步就是知道展示什么技能。那些公司们主要希望数据科学工作者拥有的技能，或者说他们主要希望作品集所展示的技能是：表达能力合作能力专业技能解释数据的能力有目标和有积极性的任何一个好的作品集都由多个工程构成，每一个工程都会展示 1-2 个上面所说的点。这是涵盖了“如何完成一个完整的数据科学作品集”系列文章的第一篇。在这篇文章中，我们将会涵括如何完成你的第一项数据科学作品集工程，并且对此进行有效的解释。在最后，你将会得到一个帮助展示你表达能力和解释数据能力的工程。用数据讲故事数据科学是表达的基础。你将会在数据中发现一些观点，并且找出一个高效的方式来向他人表达这些，之后向他们展示你所开展的课题。数据科学最关键的手法之一就是能够用数据讲述一个清晰的故事。一个清晰的故事能够使你的观点更加引人注目，并且能使别人理解你的想法。数据科学中的故事是一个讲述你发现了什么，你怎么发现它的，并且它意味着什么的故事。例如假使发现你公司的收入相对去年减少了百分之二十。这并不能够确定原因，你可能需要和其它人沟通为什么收入会减少，并且在尝试修复它。用数据讲故事主要包含：理解并确定上下文从多角度发掘使用有趣的表示方法使用多种数据来源一致的表述用来讲述数据的故事最有效率的工具就是 Jupyter notebook。如果你不熟悉，此处有一个好的教程。Jupyter notebook 允许你交互式的发掘数据，并且将你的结果分享到多个网站，包括 Github。分享你的结果有助于合作研究和其他人拓展你的分析。在这篇文章中，我们将使用 Jupyter notebook，以及 Pandas 和 matplotlib 这样的 Python 库。为你的数据科学工程选择一个主题建立一个工程的第一步就是决定你的主题。你要让你的主题是你兴趣所在，有动力去挖掘。进行数据挖掘时，为了完成而完成和有兴趣完成的区别是很明显的。这个步骤是值得花费时间的，所以确保你找到了你真正感兴趣的东西。一个寻找主题的好方法就是浏览不同的数据集并且寻找感兴趣的部分。这里有一些作为起点的好的网站：Data.gov - 包含了政府数据。/r/datasets – 一个有着上百个有趣数据集的 reddit 板块。Awesome datasets – 一个数据集的列表，位于 Github 上。17 个找到数据集的地方 – 这篇博文列出了 17 个数据集，每个都包含了示例数据集。真实世界中的数据科学，你经常无法找到可以浏览的合适的单个数据集。你可能需要聚合多个独立的数据源，或者做数量庞大的数据清理。如果该主题非常吸引你，这是值得这样做的，并且也能更好的展示你的技能。关于这篇文章的主题，我们将使用纽约市公立学校的数据，我们可以在这里找到它。选择主题这对于项目全程来说是十分重要的。因为主题能很好的限制项目的范围，并且它能够使我们知道它可以被完成。比起一个没有足够动力完成的工程来说，添加到一个完成的工程更加容易。所以，我们将关注高中的学术评估测试，伴随着多种人口统计和它们的其它数据。关于学习评估测试， 或者说 SAT，是美国高中生申请大学前的测试。大学在做判定时将考虑该成绩，所以高分是十分重要的。考试分为三个阶段，每个阶段总分为 800。全部分数为 2400（即使这个前后更改了几次，在数据中总分还是 2400）。高中经常通过平均 SAT分数进行排名，并且 SAT 是评判高中有多好的标准。因为由关于 SAT 分数对于美国中某些种族群体是不公平的，所以对纽约市这个数据做分析能够对 SAT 的公平性有些许帮助。我们在这里有 SAT 成绩的数据集，并且在这里有包含了每所高中的信息的数据集。这些将构成我们的工程的基础，但是我们将加入更多的信息来创建有趣的分析。补充数据如果你已经有了一个很好的主题，拓展其它可以提升主题或者更深入挖掘数据的的数据集是一个好的选择。在前期十分适合做这些工作，你将会有尽可能多的数据来构建你的工程。数据越少意味着你会太早的放弃了你的工程。在本项目中，在包含人口统计信息和测试成绩的网站上有一些相关的数据集。这些是我们将会用到的所有数据集：学校 SAT 成绩 – 纽约市每所高中的 SAT 成绩。学校出勤情况 – 纽约市每所学校的出勤信息。数学成绩 – 纽约市每所学校的数学成绩。班级规模 - 纽约市每所学校课堂人数信息。AP 成绩 - 高阶位考试，在美国，通过 AP 测试就能获得大学学分。毕业去向 – 由百分之几的学生毕业了，和其它去向信息。人口统计 – 每个学校的人口统计信息。学校问卷 – 学校的家长、教师，学生的问卷。学校分布地图 – 包含学校的区域布局信息，因此我们能将它们在地图上标出。（LCTT 译注：高阶位考试（AP）是美国和加拿大的一个由大学委员会创建的计划，该计划为高中学生提供大学水平的课程和考试。 美国学院和大学可以授予在考试中获得高分的学生的就学和课程学分。）这些数据作品集之间是相互关联的，并且我们能够在开始分析之前进行合并。获取背景信息在开始分析数据之前，搜索一些背景信息是有必要的。我们知道这些有用的信息：纽约市被分为五个不同的辖区纽约市的学校被分配到几个学区，每个学区都可能包含数十所学校。数据集中的学校并不全是高中，所以我们需要对数据进行一些清理工作。纽约市的每所学校都有自己单独的编码，被称为‘DBN’，即区域行政编号。为了通过区域进行数据聚合，我们可以使用地图区域信息来绘制逐区差异。理解数据为了真正的理解数据信息，你需要花费时间来挖掘和阅读数据。因此，每个数据链接都有数据的描述信息，并伴随着相关列。就像是我们拥有的高中 SAT 成绩信息，也包含图像和其它信息的数据集。我们可以运行一些代码来读取数据。我们将使用 Jupyter notebook 来挖掘数据。下面的代码将会执行以下操作：循环遍历我们下载的所有数据文件。将文件读取到 Pandas DataFrame。将所有数据框架导入 Python 数据库中。In [100]:import pandas import numpy as np files = ["ap_2010.csv", "class_size.csv", "demographics.csv", "graduation.csv", "hs_directory.csv", "math_test_results.csv", "sat_results.csv"] data = {} for f in files: d = pandas.read_csv("schools/{0}".format(f)) data[f.replace(".csv", "")] = d 一旦我们将数据读入，我们就可以使用 DataFrames 的 head 方法打印每个 DataFrame 的前五行。In [103]:for k,v in data.items(): print("\n" + k + "\n") print(v.head()) math_test_results DBN Grade Year Category Number Tested Mean Scale Score Level 1 # \ 0 01M015 3 2006 All Students 39 667 2 1 01M015 3 2007 All Students 31 672 2 2 01M015 3 2008 All Students 37 668 0 3 01M015 3 2009 All Students 33 668 0 4 01M015 3 2010 All Students 26 677 6 Level 1 % Level 2 # Level 2 % Level 3 # Level 3 % Level 4 # Level 4 % \ 0 5.1% 11 28.2% 20 51.3% 6 15.4% 1 6.5% 3 9.7% 22 71% 4 12.9% 2 0% 6 16.2% 29 78.4% 2 5.4% 3 0% 4 12.1% 28 84.8% 1 3% 4 23.1% 12 46.2% 6 23.1% 2 7.7% Level 3+4 # Level 3+4 % 0 26 66.7% 1 26 83.9% 2 31 83.8% 3 29 87.9% 4 8 30.8% ap_2010 DBN SchoolName AP Test Takers \ 0 01M448 UNIVERSITY NEIGHBORHOOD H.S. 39 1 01M450 EAST SIDE COMMUNITY HS 19 2 01M515 LOWER EASTSIDE PREP 24 3 01M539 NEW EXPLORATIONS SCI,TECH,MATH 255 4 02M296 High School of Hospitality Management s Total Exams Taken Number of Exams with scores 3 4 or 5 0 49 10 1 21 s 2 26 24 3 377 191 4 s s sat_results DBN SCHOOL NAME \ 0 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES 1 01M448 UNIVERSITY NEIGHBORHOOD HIGH SCHOOL 2 01M450 EAST SIDE COMMUNITY SCHOOL 3 01M458 FORSYTH SATELLITE ACADEMY 4 01M509 MARTA VALLE HIGH SCHOOL Num of SAT Test Takers SAT Critical Reading Avg. Score SAT Math Avg. Score \ 0 29 355 404 1 91 383 423 2 70 377 402 3 7 414 401 4 44 390 433 SAT Writing Avg. Score 0 363 1 366 2 370 3 359 4 384 class_size CSD BOROUGH SCHOOL CODE SCHOOL NAME GRADE PROGRAM TYPE \ 0 1 M M015 P.S. 015 Roberto Clemente 0K GEN ED 1 1 M M015 P.S. 015 Roberto Clemente 0K CTT 2 1 M M015 P.S. 015 Roberto Clemente 01 GEN ED 3 1 M M015 P.S. 015 Roberto Clemente 01 CTT 4 1 M M015 P.S. 015 Roberto Clemente 02 GEN ED CORE SUBJECT (MS CORE and 9-12 ONLY) CORE COURSE (MS CORE and 9-12 ONLY) \ 0 - - 1 - - 2 - - 3 - - 4 - - SERVICE CATEGORY(K-9* ONLY) NUMBER OF STUDENTS / SEATS FILLED \ 0 - 19.0 1 - 21.0 2 - 17.0 3 - 17.0 4 - 15.0 NUMBER OF SECTIONS AVERAGE CLASS SIZE SIZE OF SMALLEST CLASS \ 0 1.0 19.0 19.0 1 1.0 21.0 21.0 2 1.0 17.0 17.0 3 1.0 17.0 17.0 4 1.0 15.0 15.0 SIZE OF LARGEST CLASS DATA SOURCE SCHOOLWIDE PUPIL-TEACHER RATIO 0 19.0 ATS NaN 1 21.0 ATS NaN 2 17.0 ATS NaN 3 17.0 ATS NaN 4 15.0 ATS NaN demographics DBN Name schoolyear fl_percent frl_percent \ 0 01M015 P.S. 015 ROBERTO CLEMENTE 20052006 89.4 NaN 1 01M015 P.S. 015 ROBERTO CLEMENTE 20062007 89.4 NaN 2 01M015 P.S. 015 ROBERTO CLEMENTE 20072008 89.4 NaN 3 01M015 P.S. 015 ROBERTO CLEMENTE 20082009 89.4 NaN 4 01M015 P.S. 015 ROBERTO CLEMENTE 20092010 96.5 total_enrollment prek k grade1 grade2 ... black_num black_per \ 0 281 15 36 40 33 ... 74 26.3 1 243 15 29 39 38 ... 68 28.0 2 261 18 43 39 36 ... 77 29.5 3 252 17 37 44 32 ... 75 29.8 4 208 16 40 28 32 ... 67 32.2 hispanic_num hispanic_per white_num white_per male_num male_per female_num \ 0 189 67.3 5 1.8 158.0 56.2 123.0 1 153 63.0 4 1.6 140.0 57.6 103.0 2 157 60.2 7 2.7 143.0 54.8 118.0 3 149 59.1 7 2.8 149.0 59.1 103.0 4 118 56.7 6 2.9 124.0 59.6 84.0 female_per 0 43.8 1 42.4 2 45.2 3 40.9 4 40.4 [5 rows x 38 columns] graduation Demographic DBN School Name Cohort \ 0 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2003 1 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2004 2 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2005 3 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2006 4 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2006 Aug Total Cohort Total Grads - n Total Grads - % of cohort Total Regents - n \ 0 5 s s s 1 55 37 67.3% 17 2 64 43 67.2% 27 3 78 43 55.1% 36 4 78 44 56.4% 37 Total Regents - % of cohort Total Regents - % of grads \ 0 s s 1 30.9% 45.9% 2 42.2% 62.8% 3 46.2% 83.7% 4 47.4% 84.1% ... Regents w/o Advanced - n \ 0 ... s 1 ... 17 2 ... 27 3 ... 36 4 ... 37 Regents w/o Advanced - % of cohort Regents w/o Advanced - % of grads \ 0 s s 1 30.9% 45.9% 2 42.2% 62.8% 3 46.2% 83.7% 4 47.4% 84.1% Local - n Local - % of cohort Local - % of grads Still Enrolled - n \ 0 s s s s 1 20 36.4% 54.1% 15 2 16 25% 37.200000000000003% 9 3 7 9% 16.3% 16 4 7 9% 15.9% 15 Still Enrolled - % of cohort Dropped Out - n Dropped Out - % of cohort 0 s s s 1 27.3% 3 5.5% 2 14.1% 9 14.1% 3 20.5% 11 14.1% 4 19.2% 11 14.1% [5 rows x 23 columns] hs_directory dbn school_name boro \ 0 17K548 Brooklyn School for Music & Theatre Brooklyn 1 09X543 High School for Violin and Dance Bronx 2 09X327 Comprehensive Model School Project M.S. 327 Bronx 3 02M280 Manhattan Early College School for Advertising Manhattan 4 28Q680 Queens Gateway to Health Sciences Secondary Sc... Queens building_code phone_number fax_number grade_span_min grade_span_max \ 0 K440 718-230-6250 718-230-6262 9 12 1 X400 718-842-0687 718-589-9849 9 12 2 X240 718-294-8111 718-294-8109 6 12 3 M520 718-935-3477 NaN 9 10 4 Q695 718-969-3155 718-969-3552 6 12 expgrade_span_min expgrade_span_max \ 0 NaN NaN 1 NaN NaN 2 NaN NaN 3 9 14.0 4 NaN NaN ... \ 0 ... 1 ... 2 ... 3 ... 4 ... priority02 \ 0 Then to New York City residents 1 Then to New York City residents who attend an ... 2 Then to Bronx students or residents who attend... 3 Then to New York City residents who attend an ... 4 Then to Districts 28 and 29 students or residents priority03 \ 0 NaN 1 Then to Bronx students or residents 2 Then to New York City residents who attend an ... 3 Then to Manhattan students or residents 4 Then to Queens students or residents priority04 priority05 \ 0 NaN NaN 1 Then to New York City residents NaN 2 Then to Bronx students or residents Then to New York City residents 3 Then to New York City residents NaN 4 Then to New York City residents NaN priority06 priority07 priority08 priority09 priority10 \ 0 NaN NaN NaN NaN NaN 1 NaN NaN NaN NaN NaN 2 NaN NaN NaN NaN NaN 3 NaN NaN NaN NaN NaN 4 NaN NaN NaN NaN NaN Location 1 0 883 Classon Avenue\nBrooklyn, NY 11225\n(40.67... 1 1110 Boston Road\nBronx, NY 10456\n(40.8276026... 2 1501 Jerome Avenue\nBronx, NY 10452\n(40.84241... 3 411 Pearl Street\nNew York, NY 10038\n(40.7106... 4 160-20 Goethals Avenue\nJamaica, NY 11432\n(40... [5 rows x 58 columns] 我们可以开始在数据作品集中观察有用的部分：大部分数据集包含 DBN 列。一些条目看起来在地图上标出会很有趣，特别是 Location 1，这列在一个很长的字符串里面包含了位置信息。有些数据集会出现每所学校对应多行数据（DBN 数据重复），这意味着我们要进行预处理。统一数据为了使工作更简单，我们将需要将全部零散的数据集统一为一个。这将使我们能够快速跨数据集对比数据列。因此，我们需要找到相同的列将它们统一起来。请查看上面的输出数据， DBN 出现在多个数据集中，它看起来可以作为共同列。如果我们用 google 搜索 DBN New York City Schools，我们在此得到了结果。它解释了 DBN 是每个学校独特的编码。我们将挖掘数据集，特别是政府数据集。这通常需要做一些工作来找出每列的含义，或者每个数据集的意图。现在主要的问题是这两个数据集 class_size 和 hs_directory，没有 DBN 列。在 hs_directory 数据中是 dbn，那么我们只需重命名即可，或者将它复制到新的名为 DBN 的列中。在 class_size 数据中，我们将需要尝试不同的方法。DBN 列：In [5]:data["demographics"]["DBN"].head() Out[5]:0 01M015 1 01M015 2 01M015 3 01M015 4 01M015 Name: DBN, dtype: object 如果我们查看 class_size数据，我们将看到前五行如下：In [4]:data["class_size"].head() Out[4]: CSDBOROUGHSCHOOL CODESCHOOL NAMEGRADEPROGRAM TYPECORE SUBJECT (MS CORE and 9-12 ONLY)CORE COURSE (MS CORE and 9-12 ONLY)SERVICE CATEGORY(K-9* ONLY)NUMBER OF STUDENTS / SEATS FILLEDNUMBER OF SECTIONSAVERAGE CLASS SIZESIZE OF SMALLEST CLASSSIZE OF LARGEST CLASSDATA SOURCESCHOOLWIDE PUPIL-TEACHER RATIO01MM015P.S. 015 Roberto Clemente0KGEN ED---19.01.019.019.019.0ATSNaN11MM015P.S. 015 Roberto Clemente0KCTT---21.01.021.021.021.0ATSNaN21MM015P.S. 015 Roberto Clemente01GEN ED---17.01.017.017.017.0ATSNaN31MM015P.S. 015 Roberto Clemente01CTT---17.01.017.017.017.0ATSNaN41MM015P.S. 015 Roberto Clemente02GEN ED---15.01.015.015.015.0ATSNaN正如上面所见，DBN 实际上是 CSD、 BOROUGH 和 SCHOOL CODE 的组合。对那些不熟悉纽约市的人来说，纽约由五个行政区组成。每个行政区是一个组织单位，并且有着相当于美国大城市一样的面积。DBN 全称为行政区域编号。看起来就像 CSD 是区域，BOROUGH 是行政区，并且当与 SCHOOL CODE 合并时就组成了 DBN。这里并没有寻找像这个数据这样的内在规律的系统方法，这需要一些探索和努力来发现。现在我们已经知道了 DBN 的组成，那么我们就可以将它加入到 class_size 和 hs_directory 数据集中了：In [ ]:data["class_size"]["DBN"] = data["class_size"].apply(lambda x: "{0:02d}{1}".format(x["CSD"], x["SCHOOL CODE"]), axis=1) data["hs_directory"]["DBN"] = data["hs_directory"]["dbn"] 加入问卷最可能值得一看的数据集之一就是学生、家长和老师关于学校质量的问卷了。这些问卷包含了每所学校的安全程度、教学水平等。在我们合并数据集之前，让我们添加问卷数据。在真实世界的数据科学工程中，你经常会在分析过程中碰到有趣的数据，并且希望合并它。使用像 Jupyter notebook 一样灵活的工具将允许你快速添加一些新的代码，并且重新开始你的分析。因此，我们将添加问卷数据到我们的 data 文件夹，并且合并所有之前的数据。问卷数据分为两个文件，一个包含所有的学校，一个包含 75 学区。我们将需要写一些代码来合并它们。之后的代码我们将：使用 windows-1252 编码读取所有学校的问卷。使用 windows-1252 编码读取所有 75 号学区的问卷。添加指示每个数据集所在学区的标志。使用 DataFrame 的 concat 方法将数据集合并为一个。In [66]:survey1 = pandas.read_csv("schools/survey_all.txt", delimiter="\t", encoding='windows-1252') survey2 = pandas.read_csv("schools/survey_d75.txt", delimiter="\t", encoding='windows-1252') survey1["d75"] = False survey2["d75"] = True survey = pandas.concat([survey1, survey2], axis=0) 一旦我们将问卷合并，这里将会有一些混乱。我们希望我们合并的数据集列数最少，那么我们将可以轻易的进行列之间的对比并找出其间的关联。不幸的是，问卷数据有很多列并不是很有用：In [16]:survey.head() Out[16]: N\_pN\_sN\_tacap11acas11acat11acatot11bncomp11coms11...tq8c1tq8c2tq8c3tq8c4t\_q9tq91tq92tq93tq94tq95090.0NaN22.07.8NaN7.97.9M0157.6NaN...29.067.05.00.0NaN5.014.052.024.05.01161.0NaN34.07.8NaN9.18.4M0197.6NaN...74.021.06.00.0NaN3.06.03.078.09.02367.0NaN42.08.6NaN7.58.0M0208.3NaN...33.035.020.013.0NaN3.05.016.070.05.03151.0145.029.08.57.47.87.9M0348.25.9...21.045.028.07.0NaN0.018.032.039.011.0490.0NaN23.07.9NaN8.18.0M0637.9NaN...59.036.05.00.0NaN10.05.010.060.015.05 rows × 2773 columns我们可以通过查看数据文件夹中伴随问卷数据下载下来的文件来解决这个问题。它告诉我们们数据中重要的部分是哪些：我们可以去除 survey 数据集中多余的列：In [17]:survey["DBN"] = survey["dbn"] survey_fields = ["DBN", "rr_s", "rr_t", "rr_p", "N_s", "N_t", "N_p", "saf_p_11", "com_p_11", "eng_p_11", "aca_p_11", "saf_t_11", "com_t_11", "eng_t_10", "aca_t_11", "saf_s_11", "com_s_11", "eng_s_11", "aca_s_11", "saf_tot_11", "com_tot_11", "eng_tot_11", "aca_tot_11",] survey = survey.loc[:,survey_fields] data["survey"] = survey survey.shape Out[17]:(1702, 23) 请确保理你已经了解了每个数据集的内容和相关联的列，这能节约你之后大量的时间和精力：精简数据集如果我们查看某些数据集，包括 class_size，我们将立刻发现问题：In [18]:data["class_size"].head() Out[18]: CSDBOROUGHSCHOOL CODESCHOOL NAMEGRADEPROGRAM TYPECORE SUBJECT (MS CORE and 9-12 ONLY)CORE COURSE (MS CORE and 9-12 ONLY)SERVICE CATEGORY(K-9* ONLY)NUMBER OF STUDENTS / SEATS FILLEDNUMBER OF SECTIONSAVERAGE CLASS SIZESIZE OF SMALLEST CLASSSIZE OF LARGEST CLASSDATA SOURCESCHOOLWIDE PUPIL-TEACHER RATIODBN01MM015P.S. 015 Roberto Clemente0KGEN ED---19.01.019.019.019.0ATSNaN01M01511MM015P.S. 015 Roberto Clemente0KCTT---21.01.021.021.021.0ATSNaN01M01521MM015P.S. 015 Roberto Clemente01GEN ED---17.01.017.017.017.0ATSNaN01M01531MM015P.S. 015 Roberto Clemente01CTT---17.01.017.017.017.0ATSNaN01M01541MM015P.S. 015 Roberto Clemente02GEN ED---15.01.015.015.015.0ATSNaN01M015每所高中都有许多行（正如你所见的重复的 DBN 和 SCHOOL NAME）。然而，如果我们看向 sat_result 数据集，每所高中只有一行：In [21]:data["sat_results"].head() Out[21]: DBNSCHOOL NAMENum of SAT Test TakersSAT Critical Reading Avg. ScoreSAT Math Avg. ScoreSAT Writing Avg. Score001M292HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES29355404363101M448UNIVERSITY NEIGHBORHOOD HIGH SCHOOL91383423366201M450EAST SIDE COMMUNITY SCHOOL70377402370301M458FORSYTH SATELLITE ACADEMY7414401359401M509MARTA VALLE HIGH SCHOOL44390433384为了合并这些数据集，我们将需要找到方法将数据集精简到如 class_size 般一行对应一所高中。否则，我们将不能将 SAT 成绩与班级大小进行比较。我们通过首先更好的理解数据，然后做一些合并来完成。class_size 数据集像 GRADE 和 PROGRAM TYPE，每个学校有多个数据对应。为了将每个范围内的数据变为一个数据，我们将大部分重复行过滤掉，在下面的代码中我们将会：只从 class_size 中选择 GRADE 范围为 09-12 的行。只从 class_size 中选择 PROGRAM TYPE 是 GEN ED 的行。将 class_size 以 DBN 分组，然后取每列的平均值。重要的是，我们将找到每所学校班级大小（class_size）平均值。重置索引，将 DBN 重新加到列中。In [68]:class_size = data["class_size"] class_size = class_size[class_size["GRADE "] == "09-12"] class_size = class_size[class_size["PROGRAM TYPE"] == "GEN ED"] class_size = class_size.groupby("DBN").agg(np.mean) class_size.reset_index(inplace=True) data["class_size"] = class_size 精简其它数据集接下来，我们将需要精简 demographic 数据集。这里有每个学校收集多年的数据，所以这里每所学校有许多重复的行。我们将只选取 schoolyear 最近的可用行：In [69]:demographics = data["demographics"] demographics = demographics[demographics["schoolyear"] == 20112012] data["demographics"] = demographics 我们需要精简 math_test_results 数据集。这个数据集被 Grade 和 Year 划分。我们将只选取单一学年的一个年级。In [70]:data["math_test_results"] = data["math_test_results"][data["math_test_results"]["Year"] == 2011] data["math_test_results"] = data["math_test_results"][data["math_test_results"]["Grade"] == '8'] 最后，graduation需要被精简：In [71]:data["graduation"] = data["graduation"][data["graduation"]["Cohort"] == "2006"] data["graduation"] = data["graduation"][data["graduation"]["Demographic"] == "Total Cohort"] 在完成工程的主要部分之前数据清理和挖掘是十分重要的。有一个高质量的，一致的数据集将会使你的分析更加快速。计算变量计算变量可以通过使我们的比较更加快速来加快分析速度，并且能使我们做到本无法做到的比较。我们能做的第一件事就是从分开的列 SAT Math Avg. Score，SAT Critical Reading Avg. Score 和 SAT Writing Avg. Score 计算 SAT 成绩：将 SAT 列数值从字符转化为数字。将所有列相加以得到 sat_score，即 SAT 成绩。In [72]:cols = ['SAT Math Avg. Score', 'SAT Critical Reading Avg. Score', 'SAT Writing Avg. Score'] for c in cols: data["sat_results"][c] = data["sat_results"][c].convert_objects(convert_numeric=True) data['sat_results']['sat_score'] = data['sat_results'][cols[0]] + data['sat_results'][cols[1]] + data['sat_results'][cols[2]] 接下来，我们将需要进行每所学校的坐标位置分析，以便我们制作地图。这将使我们画出每所学校的位置。在下面的代码中，我们将会：从 Location 1 列分析出经度和维度。转化 lat（经度）和 lon（维度）为数字。In [73]:data["hs_directory"]['lat'] = data["hs_directory"]['Location 1'].apply(lambda x: x.split("\n")[-1].replace("(", "").replace(")", "").split(", ")[0]) data["hs_directory"]['lon'] = data["hs_directory"]['Location 1'].apply(lambda x: x.split("\n")[-1].replace("(", "").replace(")", "").split(", ")[1]) for c in ['lat', 'lon']: data["hs_directory"][c] = data["hs_directory"][c].convert_objects(convert_numeric=True) 现在，我们将输出每个数据集来查看我们有了什么数据：In [74]:for k,v in data.items(): print(k) print(v.head()) math_test_results DBN Grade Year Category Number Tested Mean Scale Score \ 111 01M034 8 2011 All Students 48 646 280 01M140 8 2011 All Students 61 665 346 01M184 8 2011 All Students 49 727 388 01M188 8 2011 All Students 49 658 411 01M292 8 2011 All Students 49 650 Level 1 # Level 1 % Level 2 # Level 2 % Level 3 # Level 3 % Level 4 # \ 111 15 31.3% 22 45.8% 11 22.9% 0 280 1 1.6% 43 70.5% 17 27.9% 0 346 0 0% 0 0% 5 10.2% 44 388 10 20.4% 26 53.1% 10 20.4% 3 411 15 30.6% 25 51% 7 14.3% 2 Level 4 % Level 3+4 # Level 3+4 % 111 0% 11 22.9% 280 0% 17 27.9% 346 89.8% 49 100% 388 6.1% 13 26.5% 411 4.1% 9 18.4% survey DBN rr_s rr_t rr_p N_s N_t N_p saf_p_11 com_p_11 eng_p_11 \ 0 01M015 NaN 88 60 NaN 22.0 90.0 8.5 7.6 7.5 1 01M019 NaN 100 60 NaN 34.0 161.0 8.4 7.6 7.6 2 01M020 NaN 88 73 NaN 42.0 367.0 8.9 8.3 8.3 3 01M034 89.0 73 50 145.0 29.0 151.0 8.8 8.2 8.0 4 01M063 NaN 100 60 NaN 23.0 90.0 8.7 7.9 8.1 ... eng_t_10 aca_t_11 saf_s_11 com_s_11 eng_s_11 aca_s_11 \ 0 ... NaN 7.9 NaN NaN NaN NaN 1 ... NaN 9.1 NaN NaN NaN NaN 2 ... NaN 7.5 NaN NaN NaN NaN 3 ... NaN 7.8 6.2 5.9 6.5 7.4 4 ... NaN 8.1 NaN NaN NaN NaN saf_tot_11 com_tot_11 eng_tot_11 aca_tot_11 0 8.0 7.7 7.5 7.9 1 8.5 8.1 8.2 8.4 2 8.2 7.3 7.5 8.0 3 7.3 6.7 7.1 7.9 4 8.5 7.6 7.9 8.0 [5 rows x 23 columns] ap_2010 DBN SchoolName AP Test Takers \ 0 01M448 UNIVERSITY NEIGHBORHOOD H.S. 39 1 01M450 EAST SIDE COMMUNITY HS 19 2 01M515 LOWER EASTSIDE PREP 24 3 01M539 NEW EXPLORATIONS SCI,TECH,MATH 255 4 02M296 High School of Hospitality Management s Total Exams Taken Number of Exams with scores 3 4 or 5 0 49 10 1 21 s 2 26 24 3 377 191 4 s s sat_results DBN SCHOOL NAME \ 0 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES 1 01M448 UNIVERSITY NEIGHBORHOOD HIGH SCHOOL 2 01M450 EAST SIDE COMMUNITY SCHOOL 3 01M458 FORSYTH SATELLITE ACADEMY 4 01M509 MARTA VALLE HIGH SCHOOL Num of SAT Test Takers SAT Critical Reading Avg. Score \ 0 29 355.0 1 91 383.0 2 70 377.0 3 7 414.0 4 44 390.0 SAT Math Avg. Score SAT Writing Avg. Score sat_score 0 404.0 363.0 1122.0 1 423.0 366.0 1172.0 2 402.0 370.0 1149.0 3 401.0 359.0 1174.0 4 433.0 384.0 1207.0 class_size DBN CSD NUMBER OF STUDENTS / SEATS FILLED NUMBER OF SECTIONS \ 0 01M292 1 88.0000 4.000000 1 01M332 1 46.0000 2.000000 2 01M378 1 33.0000 1.000000 3 01M448 1 105.6875 4.750000 4 01M450 1 57.6000 2.733333 AVERAGE CLASS SIZE SIZE OF SMALLEST CLASS SIZE OF LARGEST CLASS \ 0 22.564286 18.50 26.571429 1 22.000000 21.00 23.500000 2 33.000000 33.00 33.000000 3 22.231250 18.25 27.062500 4 21.200000 19.40 22.866667 SCHOOLWIDE PUPIL-TEACHER RATIO 0 NaN 1 NaN 2 NaN 3 NaN 4 NaN demographics DBN Name schoolyear \ 6 01M015 P.S. 015 ROBERTO CLEMENTE 20112012 13 01M019 P.S. 019 ASHER LEVY 20112012 20 01M020 PS 020 ANNA SILVER 20112012 27 01M034 PS 034 FRANKLIN D ROOSEVELT 20112012 35 01M063 PS 063 WILLIAM MCKINLEY 20112012 fl_percent frl_percent total_enrollment prek k grade1 grade2 \ 6 NaN 89.4 189 13 31 35 28 13 NaN 61.5 328 32 46 52 54 20 NaN 92.5 626 52 102 121 87 27 NaN 99.7 401 14 34 38 36 35 NaN 78.9 176 18 20 30 21 ... black_num black_per hispanic_num hispanic_per white_num \ 6 ... 63 33.3 109 57.7 4 13 ... 81 24.7 158 48.2 28 20 ... 55 8.8 357 57.0 16 27 ... 90 22.4 275 68.6 8 35 ... 41 23.3 110 62.5 15 white_per male_num male_per female_num female_per 6 2.1 97.0 51.3 92.0 48.7 13 8.5 147.0 44.8 181.0 55.2 20 2.6 330.0 52.7 296.0 47.3 27 2.0 204.0 50.9 197.0 49.1 35 8.5 97.0 55.1 79.0 44.9 [5 rows x 38 columns] graduation Demographic DBN School Name Cohort \ 3 Total Cohort 01M292 HENRY STREET SCHOOL FOR INTERNATIONAL 2006 10 Total Cohort 01M448 UNIVERSITY NEIGHBORHOOD HIGH SCHOOL 2006 17 Total Cohort 01M450 EAST SIDE COMMUNITY SCHOOL 2006 24 Total Cohort 01M509 MARTA VALLE HIGH SCHOOL 2006 31 Total Cohort 01M515 LOWER EAST SIDE PREPARATORY HIGH SCHO 2006 Total Cohort Total Grads - n Total Grads - % of cohort Total Regents - n \ 3 78 43 55.1% 36 10 124 53 42.7% 42 17 90 70 77.8% 67 24 84 47 56% 40 31 193 105 54.4% 91 Total Regents - % of cohort Total Regents - % of grads \ 3 46.2% 83.7% 10 33.9% 79.2% 17 74.400000000000006% 95.7% 24 47.6% 85.1% 31 47.2% 86.7% ... Regents w/o Advanced - n \ 3 ... 36 10 ... 34 17 ... 67 24 ... 23 31 ... 22 Regents w/o Advanced - % of cohort Regents w/o Advanced - % of grads \ 3 46.2% 83.7% 10 27.4% 64.2% 17 74.400000000000006% 95.7% 24 27.4% 48.9% 31 11.4% 21% Local - n Local - % of cohort Local - % of grads Still Enrolled - n \ 3 7 9% 16.3% 16 10 11 8.9% 20.8% 46 17 3 3.3% 4.3% 15 24 7 8.300000000000001% 14.9% 25 31 14 7.3% 13.3% 53 Still Enrolled - % of cohort Dropped Out - n Dropped Out - % of cohort 3 20.5% 11 14.1% 10 37.1% 20 16.100000000000001% 17 16.7% 5 5.6% 24 29.8% 5 6% 31 27.5% 35 18.100000000000001% [5 rows x 23 columns] hs_directory dbn school_name boro \ 0 17K548 Brooklyn School for Music & Theatre Brooklyn 1 09X543 High School for Violin and Dance Bronx 2 09X327 Comprehensive Model School Project M.S. 327 Bronx 3 02M280 Manhattan Early College School for Advertising Manhattan 4 28Q680 Queens Gateway to Health Sciences Secondary Sc... Queens building_code phone_number fax_number grade_span_min grade_span_max \ 0 K440 718-230-6250 718-230-6262 9 12 1 X400 718-842-0687 718-589-9849 9 12 2 X240 718-294-8111 718-294-8109 6 12 3 M520 718-935-3477 NaN 9 10 4 Q695 718-969-3155 718-969-3552 6 12 expgrade_span_min expgrade_span_max ... \ 0 NaN NaN ... 1 NaN NaN ... 2 NaN NaN ... 3 9 14.0 ... 4 NaN NaN ... priority05 priority06 priority07 priority08 \ 0 NaN NaN NaN NaN 1 NaN NaN NaN NaN 2 Then to New York City residents NaN NaN NaN 3 NaN NaN NaN NaN 4 NaN NaN NaN NaN priority09 priority10 Location 1 \ 0 NaN NaN 883 Classon Avenue\nBrooklyn, NY 11225\n(40.67... 1 NaN NaN 1110 Boston Road\nBronx, NY 10456\n(40.8276026... 2 NaN NaN 1501 Jerome Avenue\nBronx, NY 10452\n(40.84241... 3 NaN NaN 411 Pearl Street\nNew York, NY 10038\n(40.7106... 4 NaN NaN 160-20 Goethals Avenue\nJamaica, NY 11432\n(40... DBN lat lon 0 17K548 40.670299 -73.961648 1 09X543 40.827603 -73.904475 2 09X327 40.842414 -73.916162 3 02M280 40.710679 -74.000807 4 28Q680 40.718810 -73.806500 [5 rows x 61 columns] 合并数据集现在我们已经完成了全部准备工作，我们可以用 DBN 列将数据组合在一起了。最终，我们将会从原始数据集得到一个有着上百列的数据集。当我们合并它们，请注意有些数据集中会丢失了 sat_result 中出现的高中。为了解决这个问题，我们需要使用 outer 方法来合并缺少行的数据集，这样我们就不会丢失数据。在实际分析中，缺少数据是很常见的。能够展示解释和解决数据缺失的能力是构建一个作品集的重要部分。你可以在此阅读关于不同类型的合并。接下来的代码，我们将会：循环遍历 data 文件夹中的每一个条目。输出条目中的非唯一的 DBN 码数量。决定合并策略 - inner 或 outer。使用 DBN 列将条目合并到 DataFrame full 中。In [75]:flat_data_names = [k for k,v in data.items()] flat_data = [data[k] for k in flat_data_names] full = flat_data[0] for i, f in enumerate(flat_data[1:]): name = flat_data_names[i+1] print(name) print(len(f["DBN"]) - len(f["DBN"].unique())) join_type = "inner" if name in ["sat_results", "ap_2010", "graduation"]: join_type = "outer" if name not in ["math_test_results"]: full = full.merge(f, on="DBN", how=join_type) full.shape survey 0 ap_2010 1 sat_results 0 class_size 0 demographics 0 graduation 0 hs_directory 0 Out[75]:(374, 174) 添加值现在我们有了我们的 full 数据框架，我们几乎拥有分析需要的所有数据。虽然这里有一些缺少的部分。我们可能将AP 考试结果与 SAT 成绩相关联，但是我们首先需要将这些列转化为数字，然后填充缺失的数据。In [76]:cols = ['AP Test Takers ', 'Total Exams Taken', 'Number of Exams with scores 3 4 or 5'] for col in cols: full[col] = full[col].convert_objects(convert_numeric=True) full[cols] = full[cols].fillna(value=0) 然后我们将需要计算表示学校所在学区的 school_dist列。这将是我们匹配学区并且使用我们之前下载的区域地图画出地区级别的地图。In [77]:full["school_dist"] = full["DBN"].apply(lambda x: x[:2]) 最终，我们将需要用该列的平均值填充缺失的数据到 full 中。那么我们就可以计算关联了：In [79]:full = full.fillna(full.mean()) 计算关联一个挖掘数据并查看哪些列与你所关心的问题有联系的好方法来就是计算关联。这将告诉你哪列与你所关心的列更加有关联。你可以通过 Pandas DataFrames 的 corr 方法来完成。越接近 0 则关联越小。越接近 1 则正相关越强，越接近 -1 则负关联越强：In [80]:full.corr()['sat_score'] Out[80]:Year NaN Number Tested 8.127817e-02 rr_s 8.484298e-02 rr_t -6.604290e-02 rr_p 3.432778e-02 N_s 1.399443e-01 N_t 9.654314e-03 N_p 1.397405e-01 saf_p_11 1.050653e-01 com_p_11 2.107343e-02 eng_p_11 5.094925e-02 aca_p_11 5.822715e-02 saf_t_11 1.206710e-01 com_t_11 3.875666e-02 eng_t_10 NaN aca_t_11 5.250357e-02 saf_s_11 1.054050e-01 com_s_11 4.576521e-02 eng_s_11 6.303699e-02 aca_s_11 8.015700e-02 saf_tot_11 1.266955e-01 com_tot_11 4.340710e-02 eng_tot_11 5.028588e-02 aca_tot_11 7.229584e-02 AP Test Takers 5.687940e-01 Total Exams Taken 5.585421e-01 Number of Exams with scores 3 4 or 5 5.619043e-01 SAT Critical Reading Avg. Score 9.868201e-01 SAT Math Avg. Score 9.726430e-01 SAT Writing Avg. Score 9.877708e-01 ... SIZE OF SMALLEST CLASS 2.440690e-01 SIZE OF LARGEST CLASS 3.052551e-01 SCHOOLWIDE PUPIL-TEACHER RATIO NaN schoolyear NaN frl_percent -7.018217e-01 total_enrollment 3.668201e-01 ell_num -1.535745e-01 ell_percent -3.981643e-01 sped_num 3.486852e-02 sped_percent -4.413665e-01 asian_num 4.748801e-01 asian_per 5.686267e-01 black_num 2.788331e-02 black_per -2.827907e-01 hispanic_num 2.568811e-02 hispanic_per -3.926373e-01 white_num 4.490835e-01 white_per 6.100860e-01 male_num 3.245320e-01 male_per -1.101484e-01 female_num 3.876979e-01 female_per 1.101928e-01 Total Cohort 3.244785e-01 grade_span_max -2.495359e-17 expgrade_span_max NaN zip -6.312962e-02 total_students 4.066081e-01 number_programs 1.166234e-01 lat -1.198662e-01 lon -1.315241e-01 Name: sat_score, dtype: float64 这给了我们一些我们需要探索的内在规律：total_enrollment 与 sat_score 强相关，这是令人惊讶的，因为你曾经认为越小的学校越专注于学生就会取得更高的成绩。女生所占学校的比例（female_per） 与 SAT 成绩呈正相关，而男生所占学生比例（male_per）成负相关。没有问卷与 SAT 成绩成正相关。SAT 成绩有明显的种族不平等（white_per、asian_per、black_per、hispanic_per）。ell_percent 与 SAT 成绩明显负相关。每一个条目都是一个挖掘和讲述数据故事的潜在角度。设置上下文在我们开始数据挖掘之前，我们将希望设置上下文，不仅为了我们自己，也是为了其它阅读我们分析的人。一个好的方法就是建立挖掘图表或者地图。因此，我们将在地图标出所有学校的位置，这将有助于读者理解我们所探索的问题。在下面的代码中，我们将会：建立纽约市为中心的地图。为城市里的每所高中添加一个标记。显示地图。In [82]:import folium from folium import plugins schools_map = folium.Map(location=[full['lat'].mean(), full['lon'].mean()], zoom_start=10) marker_cluster = folium.MarkerCluster().add_to(schools_map) for name, row in full.iterrows(): folium.Marker([row["lat"], row["lon"]], popup="{0}: {1}".format(row["DBN"], row["school_name"])).add_to(marker_cluster) schools_map.create_map('schools.html') schools_map Out[82]:这个地图十分有用，但是不容易查看纽约哪里学校最多。因此，我们将用热力图来代替它：In [84]:schools_heatmap = folium.Map(location=[full['lat'].mean(), full['lon'].mean()], zoom_start=10) schools_heatmap.add_children(plugins.HeatMap([[row["lat"], row["lon"]] for name, row in full.iterrows()])) schools_heatmap.save("heatmap.html") schools_heatmap Out[84]:区域级别映射热力图能够很好的标出梯度，但是我们将需要更结构化的画出不同城市之间的 SAT 分数差距。学区是一个图形化这个信息的很好的方式，就像每个区域都有自己的管理者。纽约市有数十个学区，并且每个区域都是一个小的地理区域。我们可以通过学区来计算 SAT 分数，然后将它们画在地图上。在下面的代码中，我们将会：通过学区对 full 进行分组。计算每个学区的每列的平均值。去掉 school_dist 字段头部的 0，然后我们就可以匹配地理数据了。In [ ]:district_data = full.groupby("school_dist").agg(np.mean) district_data.reset_index(inplace=True) district_data["school_dist"] = district_data["school_dist"].apply(lambda x: str(int(x))) 我们现在将可以画出 SAT 在每个学区的平均值了。因此，我们将会读取 GeoJSON 中的数据，转化为每个区域的形状，然后通过 school_dist 列对每个区域图形和 SAT 成绩进行匹配。最终我们将创建一个图形：In [85]:def show_district_map(col): geo_path = 'schools/districts.geojson' districts = folium.Map(location=[full['lat'].mean(), full['lon'].mean()], zoom_start=10) districts.geo_json( geo_path=geo_path, data=district_data, columns=['school_dist', col], key_on='feature.properties.school_dist', fill_color='YlGn', fill_opacity=0.7, line_opacity=0.2, ) districts.save("districts.html") return districts show_district_map("sat_score") Out[85]:挖掘注册学生数与SAT分数现在我们已经依地区画出学校位置和 SAT 成绩确定了上下文，浏览我们分析的人将会对数据的上下文有更好的理解。现在我们已经完成了基础工作，我们可以开始从我们上面寻找关联时所提到的角度分析了。第一个分析角度是学校注册学生人数与 SAT 成绩。我们可以通过所有学校的注册学生与 SAT 成绩的散点图来分析。In [87]:%matplotlib inline full.plot.scatter(x='total_enrollment', y='sat_score') Out[87]:<matplotlib.axes._subplots.AxesSubplot at 0x10fe79978> 如你所见，底下角注册人数较低的部分有个较低 SAT 成绩的聚集。这个集群以外，SAT 成绩与全部注册人数只有轻微正相关。这个画出的关联显示了意想不到的图形.我们可以通过获取低注册人数且低SAT成绩的学校的名字进行进一步的分析。In [88]:full[(full["total_enrollment"] < 1000) & (full["sat_score"] < 1000)]["School Name"] Out[88]:34 INTERNATIONAL SCHOOL FOR LIBERAL ARTS 143 NaN 148 KINGSBRIDGE INTERNATIONAL HIGH SCHOOL 203 MULTICULTURAL HIGH SCHOOL 294 INTERNATIONAL COMMUNITY HIGH SCHOOL 304 BRONX INTERNATIONAL HIGH SCHOOL 314 NaN 317 HIGH SCHOOL OF WORLD CULTURES 320 BROOKLYN INTERNATIONAL HIGH SCHOOL 329 INTERNATIONAL HIGH SCHOOL AT PROSPECT 331 IT TAKES A VILLAGE ACADEMY 351 PAN AMERICAN INTERNATIONAL HIGH SCHOO Name: School Name, dtype: object 在 Google 上进行了一些搜索确定了这些学校大多数是为了正在学习英语而开设的，所以有这么低注册人数（规模）。这个挖掘向我们展示了并不是所有的注册人数都与 SAT 成绩有关联 - 而是与是否将英语作为第二语言学习的学生有关。挖掘英语学习者和 SAT 成绩现在我们知道英语学习者所占学校学生比例与低的 SAT 成绩有关联，我们可以探索其中的规律。ell_percent 列表示一个学校英语学习者所占的比例。我们可以制作关于这个关联的散点图。In [89]:full.plot.scatter(x='ell_percent', y='sat_score') Out[89]:<matplotlib.axes._subplots.AxesSubplot at 0x10fe824e0> 看起来这里有一组学校有着高的 ell_percentage 值并且有着低的 SAT 成绩。我们可以在学区层面调查这个关系，通过找出每个学区英语学习者所占的比例，并且查看是否与我们的学区层面的 SAT 地图所匹配：In [90]:show_district_map("ell_percent") Out[90]:我们可通过两个区域层面地图来查看，一个低 ELL（English-language）学习者比例的地区更倾向有高 SAT 成绩，反之亦然。关联问卷分数和 SAT 分数学生、家长和老师的问卷结果如果与 SAT 分数有很大的关联的假设是合理的。就例如具有高学术期望的学校倾向于有着更高的 SAT 分数是合理的。为了测这个理论，让我们画出 SAT 分数和多种问卷指标：In [91]:full.corr()["sat_score"]"rr_s", "rr_t", "rr_p", "N_s", "N_t", "N_p", "saf_tot_11", "com_tot_11", "aca_tot_11", "eng_tot_11".plot.bar() Out[91]:<matplotlib.axes._subplots.AxesSubplot at 0x114652400> 惊人的是，关联最大的两个因子是 N_p 和 N_s，它们分别是家长和学生回应的问卷。都与注册人数有着强关联，所以很可能偏离了 ell_learner。此外指标关联最强的就是 saf_t_11，这是学生、家长和老师对学校安全程度的感知。这说明了，越安全的学校，更能让学生在环境里安心学习。然而其它因子，像互动、交流和学术水平都与 SAT 分数无关，这也许表明了纽约在问卷中问了不理想的问题或者想错了因子（如果他们的目的是提高 SAT 分数的话）。挖掘种族和 SAT 分数其中一个角度就是调查种族和 SAT 分数的联系。这是一个大相关微分，将其画出来帮助我们理解到底发生了什么：In [92]:full.corr()["sat_score"]"white_per", "asian_per", "black_per", "hispanic_per".plot.bar() Out[92]:<matplotlib.axes._subplots.AxesSubplot at 0x108166ba8> 看起来更高比例的白种和亚洲学生与更高的 SAT 分数有关联，而更高比例的黑人和西班牙裔与更低的 SAT 分数有关联。对于西班牙学生，这可能因为近年的移民还是英语学习者的事实。我们可以标出学区层面的西班牙裔的比例并观察联系。In [93]:show_district_map("hispanic_per") Out[93]:看起来这里与英语学习者比例有关联，但是有必要对这种和其它种族在 SAT 分数上的差异进行挖掘。SAT 分数上的性别差异挖掘性别与 SAT 分数之间的关系是最后一个角度。我们注意更高的女生比例的学校倾向于与更高的 SAT 分数有关联。我们可以可视化为一个条形图：In [94]:full.corr()["sat_score"]"male_per", "female_per".plot.bar() Out[94]:<matplotlib.axes._subplots.AxesSubplot at 0x10774d0f0> 为了挖掘更多的关联性，我们可以制作一个 female_per 和 sat_score 的散点图：In [95]:full.plot.scatter(x='female_per', y='sat_score') Out[95]:<matplotlib.axes._subplots.AxesSubplot at 0x104715160> 看起来这里有一个高女生比例、高 SAT 成绩的簇（右上角）（LCTT 译注：此处散点图并未有如此迹象，可能数据图有误）。我们可以获取簇中学校的名字：In [96]:full[(full["female_per"] > 65) & (full["sat_score"] > 1400)]["School Name"] Out[96]:3 PROFESSIONAL PERFORMING ARTS HIGH SCH 92 ELEANOR ROOSEVELT HIGH SCHOOL 100 TALENT UNLIMITED HIGH SCHOOL 111 FIORELLO H. LAGUARDIA HIGH SCHOOL OF 229 TOWNSEND HARRIS HIGH SCHOOL 250 FRANK SINATRA SCHOOL OF THE ARTS HIGH SCHOOL 265 BARD HIGH SCHOOL EARLY COLLEGE Name: School Name, dtype: object 使用 Google 进行搜索可以知道这些是专注于表演艺术的精英学校。这些学校有着更高比例的女生和更高的 SAT 分数。这可能解释了更高的女生比例和 SAT 分数的关联，并且相反的更高的男生比例与更低的 SAT 分数。AP 成绩至今，我们关注的是人口统计角度。还有一个角度是我们通过数据来看参加高阶测试（AP）的学生和 SAT 分数。因为高学术成绩获得者倾向于有着高的 SAT 分数说明了它们是有关联的。In [98]:full["ap_avg"] = full["AP Test Takers "] / full["total_enrollment"] full.plot.scatter(x='ap_avg', y='sat_score') Out[98]:<matplotlib.axes._subplots.AxesSubplot at 0x11463a908> 看起来它们之间确实有着很强的关联。有趣的是右上角高 SAT 分数的学校有着高的 AP 测试通过比例：In [99]:full[(full["ap_avg"] > .3) & (full["sat_score"] > 1700)]["School Name"] Out[99]:92 ELEANOR ROOSEVELT HIGH SCHOOL 98 STUYVESANT HIGH SCHOOL 157 BRONX HIGH SCHOOL OF SCIENCE 161 HIGH SCHOOL OF AMERICAN STUDIES AT LE 176 BROOKLYN TECHNICAL HIGH SCHOOL 229 TOWNSEND HARRIS HIGH SCHOOL 243 QUEENS HIGH SCHOOL FOR THE SCIENCES A 260 STATEN ISLAND TECHNICAL HIGH SCHOOL Name: School Name, dtype: object 通过 google 搜索解释了那些大多是高选择性的学校，你需要经过测试才能进入。这就说明了为什么这些学校会有高的 AP 通过人数。包装故事在数据科学中，故事不可能真正完结。通过向其他人发布分析，你可以让他们拓展并且运用你的分析到他们所感兴趣的方向。比如在本文中，这里有一些角度我们没有完成，并且可以探索更加深入。一个开始讲述故事的最好方式就是尝试拓展或者复制别人已经完成的分析。如果你觉得采取这个方式，欢迎你拓展这篇文章的分析，并看看你能发现什么。如果你确实这么做了，请在下面评论，那么我就可以看到了。下一步如果你做的足够多，你看起来已经对用数据讲故事和构建你的第一个数据科学作品集有了很好的理解。一旦你完成了你的数据科学工程，发表在 Github 上是一个好的想法，这样别人就能够与你一起合作。如果你喜欢这篇文章，你可能希望阅读我们‘Build a Data Science Portfolio’系列文章的其它部分：如何搭建一个数据科学博客建立一个机器学习工程构建一个将帮助你找到工作的数据科学作品集的关键17 个你能找到其它数据科学工程数据集的地方怎样在 GitHub 上展示你的数据科学作品集via: https://www.dataquest.io/blog/data-science-portfolio-project/作者：Vik Paruchuri 译者：[Yoo-4x] 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出
构建你的数据科学作品集：机器学习项目
这是这个系列发布的第三篇关于如何构建数据 科学作品集 （ Data Science Portfolio ） 的文章。如果你喜欢这个系列并且想继续关注，你可以在订阅页面的底部找到链接。数据科学公司在决定雇佣时越来越关注你在数据科学方面的 作品集 （ Portfolio ） 。这其中的一个原因是，这样的作品集是判断某人的实际技能的最好的方法。好消息是构建这样的作品集完全要看你自己。只要你在这方面付出了努力，你一定可以取得让这些公司钦佩的作品集。构建高质量的作品集的第一步就是知道需要什么技能。公司想要在数据科学方面拥有的、他们希望你能够运用的主要技能有：沟通能力协作能力技术能力数据推理能力动机和主动性任何好的作品集都由多个项目表现出来，其中每个都能够表现出以上一到两点。这是本系列的第三篇，本系列我们主要讲包括如何打造面面俱到的数据科学作品集。在这一篇中，我们主要涵盖了如何构建组成你的作品集的第二个项目，以及如何创建一个端对端的机器学习项目。在最后，我们将拥有一个展示你的数据推理能力和技术能力的项目。如果你想看一下的话，这里有一个完整的例子。一个端到端的项目作为一个数据科学家，有时候你会拿到一个数据集并被问如何用它来讲故事。在这个时候，沟通就是非常重要的，你需要用它来完成这个事情。像我们在前一篇文章中用过的，类似 Jupyter notebook 这样的工具，将对你非常有帮助。在这里你能找到一些可以用的报告或者总结文档。不管怎样，有时候你会被要求创建一个具有操作价值的项目。具有操作价值的项目将直接影响到公司的日常业务，它会使用不止一次，经常是许多人使用。这个任务可能像这样 “创建一个算法来预测周转率”或者“创建一个模型来自动对我们的文章打标签”。在这种情况下，技术能力比讲故事更重要。你必须能够得到一个数据集，并且理解它，然后创建脚本处理该数据。这个脚本要运行的很快，占用系统资源很小。通常它可能要运行很多次，脚本的可使用性也很重要，并不仅仅是一个演示版。可使用性是指整合进操作流程，并且甚至是是面向用户的。端对端项目的主要组成部分:理解背景浏览数据并找出细微差别创建结构化项目，那样比较容易整合进操作流程运行速度快、占用系统资源小的高性能代码写好安装和使用文档以便其他人用为了有效的创建这种类型的项目，我们可能需要处理多个文件。强烈推荐使用 Atom 这样的文本编辑器或者 PyCharm 这样的 IDE。这些工具允许你在文件间跳转，编辑不同类型的文件，例如 markdown 文件，Python 文件，和 csv 文件等等。结构化你的项目还利于版本控制，并上传一个类似 Github 这样的协作开发工具上也很有用。Github 上的这个项目在这一节中我们将使用 Pandas 和 scikit-learn 这样的库，我们还将大量用到 Pandas DataFrames，它使得 python 读取和处理表格数据更加方便。找到好的数据集为一个端到端的作品集项目的找到好的数据集很难。在内存和性能的限制下，数据集需要尽量的大。它还需要是实际有用的。例如，这个数据集，它包含有美国院校的录取标准、毕业率以及毕业以后的收入，是个很好的可以讲故事的数据集。但是，不管你如何看待这个数据，很显然它不适合创建端到端的项目。比如，你能告诉人们他们去了这些大学以后的未来收入，但是这个快速检索却并不足够呈现出你的技术能力。你还能找出院校的招生标准和更高的收入相关，但是这更像是常理而不是你的技术结论。这里还有内存和性能约束的问题，比如你有几千兆的数据，而且当你需要找到一些差异时，就需要对数据集一遍遍运行算法。一个好的可操作的数据集可以让你构建一系列脚本来转换数据、动态地回答问题。一个很好的例子是股票价格数据集，当股市关闭时，就会给算法提供新的数据。这可以让你预测明天的股价，甚至预测收益。这不是讲故事，它带来的是真金白银。一些找到数据集的好地方：/r/datasets – 有上百的有趣数据的 subreddit（Reddit 是国外一个社交新闻站点，subreddit 指该论坛下的各不同版块）。Google Public Datasets – 通过 Google BigQuery 使用的公开数据集。Awesome datasets – 一个数据集列表，放在 Github 上。当你查看这些数据集时，想一下人们想要在这些数据集中得到什么答案，哪怕这些问题只想过一次（“房价是如何与标准普尔 500 指数关联的?”），或者更进一步（“你能预测股市吗?”）。这里的关键是更进一步地找出问题，并且用相同的代码在不同输入（不同的数据）上运行多次。对于本文的目标，我们来看一下 房利美 （ Fannie Mae ） 贷款数据。房利美是一家在美国的政府赞助的企业抵押贷款公司，它从其他银行购买按揭贷款，然后捆绑这些贷款为贷款证券来转卖它们。这使得贷款机构可以提供更多的抵押贷款，在市场上创造更多的流动性。这在理论上会带来更多的住房和更好的贷款期限。从借款人的角度来说，它们大体上差不多，话虽这样说。房利美发布了两种类型的数据 – 它获得的贷款的数据，和贷款偿还情况的数据。在理想的情况下，有人向贷款人借钱，然后还款直到还清。不管怎样，有些人多次不还，从而丧失了抵押品赎回权。抵押品赎回权是指没钱还了被银行把房子给收走了。房利美会追踪谁没还钱，并且哪个贷款需要收回抵押的房屋（取消赎回权）。每个季度会发布此数据，发布的是滞后一年的数据。当前可用是 2015 年第一季度数据。“贷款数据”是由房利美发布的贷款发放的数据，它包含借款人的信息、信用评分，和他们的家庭贷款信息。“执行数据”，贷款发放后的每一个季度公布，包含借贷人的还款信息和是否丧失抵押品赎回权的状态，一个“贷款数据”的“执行数据”可能有十几行。可以这样理解，“贷款数据”告诉你房利美所控制的贷款，“执行数据”包含该贷款一系列的状态更新。其中一个状态更新可以告诉我们一笔贷款在某个季度被取消赎回权了。一个没有及时还贷的房子就这样的被卖了选择一个角度这里有几个我们可以去分析房利美数据集的方向。我们可以：预测房屋的销售价格。预测借款人还款历史。在获得贷款时为每一笔贷款打分。最重要的事情是坚持单一的角度。同时关注太多的事情很难做出效果。选择一个有着足够细节的角度也很重要。下面的角度就没有太多细节：找出哪些银行将贷款出售给房利美的多数被取消赎回权。计算贷款人的信用评分趋势。找到哪些类型的家庭没有偿还贷款的能力。找到贷款金额和抵押品价格之间的关系。上面的想法非常有趣，如果我们关注于讲故事，那是一个不错的角度，但是不是很适合一个操作性项目。在房利美数据集中，我们将仅使用申请贷款时有的那些信息来预测贷款是否将来会被取消赎回权。实际上, 我们将为每一笔贷款建立“分数”来告诉房利美买还是不买。这将给我们打下良好的基础，并将组成这个漂亮的作品集的一部分。理解数据我们来简单看一下原始数据文件。下面是 2012 年 1 季度前几行的贷款数据：100000853384|R|OTHER|4.625|280000|360|02/2012|04/2012|31|31|1|23|801|N|C|SF|1|I|CA|945||FRM| 100003735682|R|SUNTRUST MORTGAGE INC.|3.99|466000|360|01/2012|03/2012|80|80|2|30|794|N|P|SF|1|P|MD|208||FRM|788 100006367485|C|PHH MORTGAGE CORPORATION|4|229000|360|02/2012|04/2012|67|67|2|36|802|N|R|SF|1|P|CA|959||FRM|794 下面是 2012 年 1 季度的前几行执行数据：100000853384|03/01/2012|OTHER|4.625||0|360|359|03/2042|41860|0|N|||||||||||||||| 100000853384|04/01/2012||4.625||1|359|358|03/2042|41860|0|N|||||||||||||||| 100000853384|05/01/2012||4.625||2|358|357|03/2042|41860|0|N|||||||||||||||| 在开始编码之前，花些时间真正理解数据是值得的。这对于操作性项目优为重要，因为我们没有交互式探索数据，将很难察觉到细微的差别，除非我们在前期发现他们。在这种情况下，第一个步骤是阅读房利美站点的资料：概述有用的术语表常见问答贷款和执行文件中的列贷款数据文件样本执行数据文件样本在看完这些文件后后，我们了解到一些能帮助我们的关键点：从 2000 年到现在，每季度都有一个贷款和执行文件，因数据是滞后一年的，所以到目前为止最新数据是 2015 年的。这些文件是文本格式的，采用管道符号|进行分割。这些文件是没有表头的，但我们有个文件列明了各列的名称。所有一起，文件包含 2200 万个贷款的数据。由于执行数据的文件包含过去几年获得的贷款的信息，在早些年获得的贷款将有更多的执行数据（即在 2014 获得的贷款没有多少历史执行数据）。这些小小的信息将会为我们节省很多时间，因为这样我们就知道如何构造我们的项目和利用这些数据了。构造项目在我们开始下载和探索数据之前，先想一想将如何构造项目是很重要的。当建立端到端项目时，我们的主要目标是：创建一个可行解决方案有一个快速运行且占用最小资源的解决方案容易可扩展写容易理解的代码写尽量少的代码为了实现这些目标，需要对我们的项目进行良好的构造。一个结构良好的项目遵循几个原则：分离数据文件和代码文件从原始数据中分离生成的数据。有一个 README.md 文件帮助人们安装和使用该项目。有一个 requirements.txt 文件列明项目运行所需的所有包。有一个单独的 settings.py 文件列明其它文件中使用的所有的设置例如，如果从多个 Python 脚本读取同一个文件，让它们全部 import 设置并从一个集中的地方获得文件名是有用的。有一个 .gitignore 文件，防止大的或密码文件被提交。分解任务中每一步可以单独执行的步骤到单独的文件中。例如，我们将有一个文件用于读取数据，一个用于创建特征，一个用于做出预测。保存中间结果，例如，一个脚本可以输出下一个脚本可读取的文件。这使我们无需重新计算就可以在数据处理流程中进行更改。我们的文件结构大体如下：loan-prediction ├── data ├── processed ├── .gitignore ├── README.md ├── requirements.txt ├── settings.py 创建初始文件首先，我们需要创建一个 loan-prediction 文件夹，在此文件夹下面，再创建一个 data 文件夹和一个 processed 文件夹。data 文件夹存放原始数据，processed 文件夹存放所有的中间计算结果。其次，创建 .gitignore 文件，.gitignore 文件将保证某些文件被 git 忽略而不会被推送至 GitHub。关于这个文件的一个好的例子是由 OSX 在每一个文件夹都会创建的 .DS_Store 文件，.gitignore 文件一个很好的范本在这里。我们还想忽略数据文件，因为它们实在是太大了，同时房利美的条文禁止我们重新分发该数据文件，所以我们应该在我们的文件后面添加以下 2 行：data processed 这里是该项目的一个关于 .gitignore 文件的例子。再次，我们需要创建 README.md 文件，它将帮助人们理解该项目。后缀 .md 表示这个文件采用 markdown 格式。Markdown 使你能够写纯文本文件，同时还可以添加你想要的神奇的格式。这里是关于 markdown 的导引。如果你上传一个叫 README.md 的文件至 Github，Github 会自动处理该 markdown，同时展示给浏览该项目的人。例子在这里。至此，我们仅需在 README.md 文件中添加简单的描述：Loan Prediction ----------------------- Predict whether or not loans acquired by Fannie Mae will go into foreclosure. Fannie Mae acquires loans from other lenders as a way of inducing them to lend more. Fannie Mae releases data on the loans it has acquired and their performance afterwards [here](http://www.fanniemae.com/portal/funding-the-market/data/loan-performance-data.html). 现在，我们可以创建 requirements.txt 文件了。这会帮助其它人可以很方便地安装我们的项目。我们还不知道我们将会具体用到哪些库，但是以下几个库是需要的：pandas matplotlib scikit-learn numpy ipython scipy 以上几个是在 python 数据分析任务中最常用到的库。可以认为我们将会用到大部分这些库。这里是该项目 requirements.txt 文件的一个例子。创建 requirements.txt 文件之后，你应该安装这些包了。我们将会使用 python3。如果你没有安装 python，你应该考虑使用 Anaconda，它是一个 python 安装程序，同时安装了上面列出的所有包。最后，我们可以建立一个空白的 settings.py 文件，因为我们的项目还没有任何设置。获取数据一旦我们有了项目的基本架构，我们就可以去获得原始数据。房利美对获取数据有一些限制，所以你需要去注册一个账户。在创建完账户之后，你可以找到在这里的下载页面，你可以按照你所需要的下载或多或少的贷款数据文件。文件格式是 zip，在解压后当然是非常大的。为了达到我们这个文章的目的，我们将要下载从 2012 年 1 季度到 2015 年 1 季度的所有数据。接着我们需要解压所有的文件。解压过后，删掉原来的 .zip 格式的文件。最后，loan-prediction 文件夹看起来应该像下面的一样：loan-prediction ├── data │ ├── Acquisition_2012Q1.txt │ ├── Acquisition_2012Q2.txt │ ├── Performance_2012Q1.txt │ ├── Performance_2012Q2.txt │ └── ... ├── processed ├── .gitignore ├── README.md ├── requirements.txt ├── settings.py 在下载完数据后，你可以在 shell 命令行中使用 head 和 tail 命令去查看文件中的行数据，你看到任何的不需要的数据列了吗？在做这件事的同时查阅列名称的 pdf 文件可能有帮助。读入数据有两个问题让我们的数据难以现在就使用：贷款数据和执行数据被分割在多个文件中每个文件都缺少列名标题在我们开始使用数据之前，我们需要首先明白我们要在哪里去存一个贷款数据的文件，同时到哪里去存储一个执行数据的文件。每个文件仅仅需要包括我们关注的那些数据列，同时拥有正确的列名标题。这里有一个小问题是执行数据非常大，因此我们需要尝试去修剪一些数据列。第一步是向 settings.py 文件中增加一些变量，这个文件中同时也包括了我们原始数据的存放路径和处理出的数据存放路径。我们同时也将添加其他一些可能在接下来会用到的设置数据：DATA_DIR = "data" PROCESSED_DIR = "processed" MINIMUM_TRACKING_QUARTERS = 4 TARGET = "foreclosure_status" NON_PREDICTORS = [TARGET, "id"] CV_FOLDS = 3 把路径设置在 settings.py 中使它们放在一个集中的地方，同时使其修改更加的容易。当在多个文件中用到相同的变量时，你想改变它的话，把他们放在一个地方比分散放在每一个文件时更加容易。这里的是一个这个工程的示例 settings.py 文件第二步是创建一个文件名为 assemble.py，它将所有的数据分为 2 个文件。当我们运行 Python assemble.py，我们在处理数据文件的目录会获得 2 个数据文件。接下来我们开始写 assemble.py 文件中的代码。首先我们需要为每个文件定义相应的列名标题，因此我们需要查看列名称的 pdf 文件，同时创建在每一个贷款数据和执行数据的文件的数据列的列表：HEADERS = { "Acquisition": [ "id", "channel", "seller", "interest_rate", "balance", "loan_term", "origination_date", "first_payment_date", "ltv", "cltv", "borrower_count", "dti", "borrower_credit_score", "first_time_homebuyer", "loan_purpose", "property_type", "unit_count", "occupancy_status", "property_state", "zip", "insurance_percentage", "product_type", "co_borrower_credit_score" ], "Performance": [ "id", "reporting_period", "servicer_name", "interest_rate", "balance", "loan_age", "months_to_maturity", "maturity_date", "msa", "delinquency_status", "modification_flag", "zero_balance_code", "zero_balance_date", "last_paid_installment_date", "foreclosure_date", "disposition_date", "foreclosure_costs", "property_repair_costs", "recovery_costs", "misc_costs", "tax_costs", "sale_proceeds", "credit_enhancement_proceeds", "repurchase_proceeds", "other_foreclosure_proceeds", "non_interest_bearing_balance", "principal_forgiveness_balance" ] } 接下来一步是定义我们想要保留的数据列。因为我们要预测一个贷款是否会被撤回，我们可以丢弃执行数据中的许多列。我们将需要保留贷款数据中的所有数据列，因为我们需要尽量多的了解贷款发放时的信息（毕竟我们是在预测贷款发放时这笔贷款将来是否会被撤回）。丢弃数据列将会使我们节省下内存和硬盘空间，同时也会加速我们的代码。SELECT = { "Acquisition": HEADERS["Acquisition"], "Performance": [ "id", "foreclosure_date" ] } 下一步，我们将编写一个函数来连接数据集。下面的代码将：引用一些需要的库，包括 settings。定义一个函数 concatenate，目的是：获取到所有 data 目录中的文件名。遍历每个文件。如果文件不是正确的格式 (不是以我们需要的格式作为开头)，我们将忽略它。通过使用 Pandas 的 read\_csv 函数及正确的设置把文件读入一个 DataFrame。设置分隔符为｜，以便所有的字段能被正确读出。数据没有标题行，因此设置 header 为 None 来进行标示。从 HEADERS 字典中设置正确的标题名称 – 这将会是我们的 DataFrame 中的数据列名称。仅选择我们加在 SELECT 中的 DataFrame 的列。把所有的 DataFrame 共同连接在一起。把已经连接好的 DataFrame 写回一个文件。import os import settings import pandas as pd def concatenate(prefix="Acquisition"): files = os.listdir(settings.DATA_DIR) full = [] for f in files: if not f.startswith(prefix): continue data = pd.read_csv(os.path.join(settings.DATA_DIR, f), sep="|", header=None, names=HEADERS[prefix], index_col=False) data = data[SELECT[prefix]] full.append(data) full = pd.concat(full, axis=0) full.to_csv(os.path.join(settings.PROCESSED_DIR, "{}.txt".format(prefix)), sep="|", header=SELECT[prefix], index=False) 我们可以通过调用上面的函数，通过传递的参数 Acquisition 和 Performance 两次以将所有的贷款和执行文件连接在一起。下面的代码将：仅在命令行中运行 python assemble.py 时执行。将所有的数据连接在一起，并且产生 2 个文件：processed/Acquisition.txtprocessed/Performance.txtif __name__ == "__main__": concatenate("Acquisition") concatenate("Performance") 我们现在拥有了一个漂亮的，划分过的 assemble.py 文件，它很容易执行，也容易建立。通过像这样把问题分解为一块一块的，我们构建工程就会变的容易许多。不用一个可以做所有工作的凌乱脚本，我们定义的数据将会在多个脚本间传递，同时使脚本间完全的彼此隔离。当你正在一个大的项目中工作时，这样做是一个好的想法，因为这样可以更加容易修改其中的某一部分而不会引起其他项目中不关联部分产生预料之外的结果。一旦我们完成 assemble.py 脚本文件，我们可以运行 python assemble.py 命令。你可以在这里查看完整的 assemble.py 文件。这将会在 processed 目录下产生 2 个文件：loan-prediction ├── data │ ├── Acquisition_2012Q1.txt │ ├── Acquisition_2012Q2.txt │ ├── Performance_2012Q1.txt │ ├── Performance_2012Q2.txt │ └── ... ├── processed │ ├── Acquisition.txt │ ├── Performance.txt ├── .gitignore ├── assemble.py ├── README.md ├── requirements.txt ├── settings.py 计算来自执行数据的值接下来我们会计算来自 processed/Performance.txt 中的值。我们要做的就是推测这些资产是否被取消赎回权。如果能够计算出来，我们只要看一下关联到贷款的执行数据的参数 foreclosure_date 就可以了。如果这个参数的值是 None，那么这些资产肯定没有收回。为了避免我们的样例中只有少量的执行数据，我们会为每个贷款计算出执行数据文件中的行数。这样我们就能够从我们的训练数据中筛选出贷款数据，排除了一些执行数据。下面是我认为贷款数据和执行数据的关系：在上面的表格中，贷款数据中的每一行数据都关联到执行数据中的多行数据。在执行数据中，在取消赎回权的时候 foreclosure_date 就会出现在该季度，而之前它是空的。一些贷款还没有被取消赎回权，所以与执行数据中的贷款数据有关的行在 foreclosure_date 列都是空格。我们需要计算 foreclosure_status 的值，它的值是布尔类型，可以表示一个特殊的贷款数据 id 是否被取消赎回权过，还有一个参数 performance_count ，它记录了执行数据中每个贷款 id 出现的行数。 计算这些行数有多种不同的方法：我们能够读取所有的执行数据，然后我们用 Pandas 的 groupby 方法在 DataFrame 中计算出与每个贷款 id 有关的行的行数，然后就可以查看贷款 id 的 foreclosure_date 值是否为 None 。这种方法的优点是从语法上来说容易执行。它的缺点需要读取所有的 129236094 行数据，这样就会占用大量内存，并且运行起来极慢。我们可以读取所有的执行数据，然后在贷款 DataFrame 上使用 apply 去计算每个贷款 id 出现的次数。这种方法的优点是容易理解。缺点是需要读取所有的 129236094 行数据。这样会占用大量内存，并且运行起来极慢。我们可以在迭代访问执行数据中的每一行数据，而且会建立一个单独的计数字典。这种方法的优点是数据不需要被加载到内存中，所以运行起来会很快且不需要占用内存。缺点是这样的话理解和执行上可能有点耗费时间，我们需要对每一行数据进行语法分析。加载所有的数据会非常耗费内存，所以我们采用第三种方法。我们要做的就是迭代执行数据中的每一行数据，然后为每一个贷款 id 在字典中保留一个计数。在这个字典中，我们会计算出贷款 id 在执行数据中出现的次数，而且看看 foreclosure_date 是否是 None 。我们可以查看 foreclosure_status 和 performance_count 的值 。我们会新建一个 annotate.py 文件，文件中的代码可以计算这些值。我们会使用下面的代码：导入需要的库定义一个函数 count_performance_rows 。打开 processed/Performance.txt 文件。这不是在内存中读取文件而是打开了一个文件标识符，这个标识符可以用来以行为单位读取文件。迭代文件的每一行数据。使用分隔符|分开每行的不同数据。检查 loan_id 是否在计数字典中。如果不存在，把它加进去。loan_id 的 performance_count 参数自增 1 次，因为我们这次迭代也包含其中。如果 date 不是 None ，我们就会知道贷款被取消赎回权了，然后为foreclosure\_status` 设置合适的值。import os import settings import pandas as pd def count_performance_rows(): counts = {} with open(os.path.join(settings.PROCESSED_DIR, "Performance.txt"), 'r') as f: for i, line in enumerate(f): if i == 0: # Skip header row continue loan_id, date = line.split("|") loan_id = int(loan_id) if loan_id not in counts: counts[loan_id] = { "foreclosure_status": False, "performance_count": 0 } counts[loan_id]["performance_count"] += 1 if len(date.strip()) > 0: counts[loan_id]["foreclosure_status"] = True return counts 获取值只要我们创建了计数字典，我们就可以使用一个函数通过一个 loan_id 和一个 key 从字典中提取到需要的参数的值：def get_performance_summary_value(loan_id, key, counts): value = counts.get(loan_id, { "foreclosure_status": False, "performance_count": 0 }) return value[key] 上面的函数会从 counts 字典中返回合适的值，我们也能够为贷款数据中的每一行赋一个 foreclosure_status 值和一个 performance_count 值。如果键不存在，字典的 get 方法会返回一个默认值，所以在字典中不存在键的时候我们就可以得到一个可知的默认值。转换数据我们已经在 annotate.py 中添加了一些功能，现在我们来看一看数据文件。我们需要将贷款到的数据转换到训练数据集来进行机器学习算法的训练。这涉及到以下几件事情:转换所有列为数字。填充缺失值。为每一行分配 performance_count 和 foreclosure_status。移除出现执行数据很少的行（performance_count 计数低）。我们有几个列是文本类型的，看起来对于机器学习算法来说并不是很有用。然而，它们实际上是分类变量，其中有很多不同的类别代码，例如 R，S 等等. 我们可以把这些类别标签转换为数值：通过这种方法转换的列我们可以应用到机器学习算法中。还有一些包含日期的列 (first_payment_date 和 origination_date）。我们可以将这些日期放到两个列中：在下面的代码中，我们将转换贷款数据。我们将定义一个函数如下：在 acquisition 中创建 foreclosure_status 列，并从 counts 字典中得到值。在 acquisition 中创建 performance_count 列，并从 counts 字典中得到值。将下面的列从字符串转换为整数：channelsellerfirst_time_homebuyerloan_purposeproperty_typeoccupancy_statusproperty_stateproduct_type将 first_payment_date 和 origination_date 分别转换为两列：通过斜杠分离列。将第一部分分离成 month 列。将第二部分分离成 year 列。删除该列。最后，我们得到 first_payment_month、first_payment_year、rigination_month 和 origination_year。所有缺失值填充为 -1。def annotate(acquisition, counts): acquisition["foreclosure_status"] = acquisition["id"].apply(lambda x: get_performance_summary_value(x, "foreclosure_status", counts)) acquisition["performance_count"] = acquisition["id"].apply(lambda x: get_performance_summary_value(x, "performance_count", counts)) for column in [ "channel", "seller", "first_time_homebuyer", "loan_purpose", "property_type", "occupancy_status", "property_state", "product_type" ]: acquisition[column] = acquisition[column].astype('category').cat.codes for start in ["first_payment", "origination"]: column = "{}_date".format(start) acquisition["{}_year".format(start)] = pd.to_numeric(acquisition[column].str.split('/').str.get(1)) acquisition["{}_month".format(start)] = pd.to_numeric(acquisition[column].str.split('/').str.get(0)) del acquisition[column] acquisition = acquisition.fillna(-1) acquisition = acquisition[acquisition["performance_count"] > settings.MINIMUM_TRACKING_QUARTERS] return acquisition 聚合到一起我们差不多准备就绪了，我们只需要再在 annotate.py 添加一点点代码。在下面代码中，我们将：定义一个函数来读取贷款的数据。定义一个函数来写入处理过的数据到 processed/train.csv。如果该文件在命令行以 python annotate.py 的方式运行：读取贷款数据。计算执行数据的计数，并将其赋予 counts。转换 acquisition DataFrame。将acquisition DataFrame 写入到 train.csv。def read(): acquisition = pd.read_csv(os.path.join(settings.PROCESSED_DIR, "Acquisition.txt"), sep="|") return acquisition def write(acquisition): acquisition.to_csv(os.path.join(settings.PROCESSED_DIR, "train.csv"), index=False) if __name__ == "__main__": acquisition = read() counts = count_performance_rows() acquisition = annotate(acquisition, counts) write(acquisition) 修改完成以后，确保运行 python annotate.py 来生成 train.csv 文件。 你可以在这里找到完整的 annotate.py 文件。现在文件夹看起来应该像这样：loan-prediction ├── data │ ├── Acquisition_2012Q1.txt │ ├── Acquisition_2012Q2.txt │ ├── Performance_2012Q1.txt │ ├── Performance_2012Q2.txt │ └── ... ├── processed │ ├── Acquisition.txt │ ├── Performance.txt │ ├── train.csv ├── .gitignore ├── annotate.py ├── assemble.py ├── README.md ├── requirements.txt ├── settings.py 找到误差标准我们已经完成了训练数据表的生成，现在我们需要最后一步，生成预测。我们需要找到误差的标准，以及该如何评估我们的数据。在这种情况下，因为有很多的贷款没有被取消赎回权，所以根本不可能做到精确的计算。我们需要读取训练数据，并且计算 foreclosure_status 列的计数，我们将得到如下信息：import pandas as pd import settings train = pd.read_csv(os.path.join(settings.PROCESSED_DIR, "train.csv")) train["foreclosure_status"].value_counts() False 4635982 True 1585 Name: foreclosure_status, dtype: int64 因为只有很少的贷款被取消赎回权，只需要检查正确预测的标签的百分比就意味着我们可以创建一个机器学习模型，来为每一行预测 False，并能取得很高的精确度。相反，我们想要使用一个度量来考虑分类的不平衡，确保我们可以准确预测。我们要避免太多的误报率（预测贷款被取消赎回权，但是实际没有），也要避免太多的漏报率（预测贷款没有别取消赎回权，但是实际被取消了）。对于这两个来说，漏报率对于房利美来说成本更高，因为他们买的贷款可能是他们无法收回投资的贷款。所以我们将定义一个漏报率，就是模型预测没有取消赎回权但是实际上取消了，这个数除以总的取消赎回权数。这是“缺失的”实际取消赎回权百分比的模型。下面看这个图表：通过上面的图表，有 1 个贷款预测不会取消赎回权，但是实际上取消了。如果我们将这个数除以实际取消赎回权的总数 2，我们将得到漏报率 50%。 我们将使用这个误差标准，因此我们可以评估一下模型的行为。设置机器学习分类器我们使用交叉验证预测。通过交叉验证法，我们将数据分为3组。按照下面的方法来做：用组 1 和组 2 训练模型，然后用该模型来预测组 3用组 1 和组 3 训练模型，然后用该模型来预测组 2用组 2 和组 3 训练模型，然后用该模型来预测组 1将它们分割到不同的组，这意味着我们永远不会用相同的数据来为其预测训练模型。这样就避免了过拟合。如果过拟合，我们将错误地拉低了漏报率，这使得我们难以改进算法或者应用到现实生活中。Scikit-learn 有一个叫做 crossvalpredict ，它可以帮助我们理解交叉算法.我们还需要一种算法来帮我们预测。我们还需要一个分类器来做二元分类。目标变量 foreclosure_status 只有两个值， True 和 False。这里我们用 逻辑回归算法，因为它能很好的进行二元分类，并且运行很快，占用内存很小。我们来说一下它是如何工作的：不使用像随机森林一样多树结构，也不像支持向量机一样做复杂的转换，逻辑回归算法涉及更少的步骤和更少的矩阵。我们可以使用 scikit-learn 实现的逻辑回归分类器算法。我们唯一需要注意的是每个类的权重。如果我们使用等权重的类，算法将会预测每行都为 false，因为它总是试图最小化误差。不管怎样，我们重视有多少贷款要被取消赎回权而不是有多少不能被取消。因此，我们给逻辑回归类的 class_weight 关键字传递 balanced 参数，让算法可以为不同 counts 的每个类考虑不同的取消赎回权的权重。这将使我们的算法不会为每一行都预测 false，而是两个类的误差水平一致。做出预测既然完成了前期准备，我们可以开始准备做出预测了。我将创建一个名为 predict.py 的新文件，它会使用我们在最后一步创建的 train.csv 文件。下面的代码：导入所需的库创建一个名为 cross_validate 的函数：使用正确的关键词参数创建逻辑回归分类器创建用于训练模型的数据列的列表，移除 id 和 foreclosure_status 列交叉验证 train DataFrame返回预测结果import os import settings import pandas as pd from sklearn import cross_validation from sklearn.linear_model import LogisticRegression from sklearn import metrics def cross_validate(train): clf = LogisticRegression(random_state=1, class_weight="balanced") predictors = train.columns.tolist() predictors = [p for p in predictors if p not in settings.NON_PREDICTORS] predictions = cross_validation.cross_val_predict(clf, train[predictors], train[settings.TARGET], cv=settings.CV_FOLDS) return predictions 预测误差现在，我们仅仅需要写一些函数来计算误差。下面的代码：创建函数 compute_error：使用 scikit-learn 计算一个简单的精确分数（与实际 foreclosure_status 值匹配的预测百分比）创建函数 compute_false_negatives：为了方便，将目标和预测结果合并到一个 DataFrame查找漏报率找到原本应被预测模型取消赎回权，但实际没有取消的贷款数目除以没被取消赎回权的贷款总数目def compute_error(target, predictions): return metrics.accuracy_score(target, predictions) def compute_false_negatives(target, predictions): df = pd.DataFrame({"target": target, "predictions": predictions}) return df[(df["target"] == 1) & (df["predictions"] == 0)].shape[0] / (df[(df["target"] == 1)].shape[0] + 1) def compute_false_positives(target, predictions): df = pd.DataFrame({"target": target, "predictions": predictions}) return df[(df["target"] == 0) & (df["predictions"] == 1)].shape[0] / (df[(df["target"] == 0)].shape[0] + 1) 聚合到一起现在，我们可以把函数都放在 predict.py。下面的代码：读取数据集计算交叉验证预测计算上面的 3 个误差打印误差def read(): train = pd.read_csv(os.path.join(settings.PROCESSED_DIR, "train.csv")) return train if __name__ == "__main__": train = read() predictions = cross_validate(train) error = compute_error(train[settings.TARGET], predictions) fn = compute_false_negatives(train[settings.TARGET], predictions) fp = compute_false_positives(train[settings.TARGET], predictions) print("Accuracy Score: {}".format(error)) print("False Negatives: {}".format(fn)) print("False Positives: {}".format(fp)) 一旦你添加完代码，你可以运行 python predict.py 来产生预测结果。运行结果向我们展示漏报率为 .26，这意味着我们没能预测 26% 的取消贷款。这是一个好的开始，但仍有很多改善的地方！你可以在这里找到完整的 predict.py 文件。你的文件树现在看起来像下面这样：loan-prediction ├── data │ ├── Acquisition_2012Q1.txt │ ├── Acquisition_2012Q2.txt │ ├── Performance_2012Q1.txt │ ├── Performance_2012Q2.txt │ └── ... ├── processed │ ├── Acquisition.txt │ ├── Performance.txt │ ├── train.csv ├── .gitignore ├── annotate.py ├── assemble.py ├── predict.py ├── README.md ├── requirements.txt ├── settings.py 撰写 README既然我们完成了端到端的项目，那么我们可以撰写 README.md 文件了，这样其他人便可以知道我们做的事，以及如何复制它。一个项目典型的 README.md 应该包括这些部分：一个高水准的项目概览，并介绍项目目的任何必需的数据和材料的下载地址安装命令如何安装要求依赖使用命令如何运行项目每一步之后会看到的结果如何为这个项目作贡献扩展项目的下一步计划这里 是这个项目的一个 README.md 样例。下一步恭喜你完成了端到端的机器学习项目！你可以在这里找到一个完整的示例项目。一旦你完成了项目，把它上传到 Github 是一个不错的主意，这样其他人也可以看到你的文件夹的部分项目。这里仍有一些留待探索数据的角度。总的来说，我们可以把它们分割为 3 类： 扩展这个项目并使它更加精确，发现其他可以预测的列，并探索数据。这是其中一些想法：在 annotate.py 中生成更多的特性切换 predict.py 中的算法尝试使用比我们发表在这里的更多的房利美数据添加对未来数据进行预测的方法。如果我们添加更多数据，我们所写的代码仍然可以起作用，这样我们可以添加更多过去和未来的数据。尝试看看是否你能预测一个银行原本是否应该发放贷款（相对地，房利美是否应该获得贷款）移除 train 中银行在发放贷款时间的不知道的任何列当房利美购买贷款时，一些列是已知的，但之前是不知道的做出预测探索是否你可以预测除了 foreclosure_status 的其他列你可以预测在销售时资产值是多少？探索探索执行数据更新之间的细微差别你能否预测借款人会逾期还款多少次?你能否标出的典型贷款周期?将数据按州或邮政编码标出你看到一些有趣的模式了吗?如果你建立了任何有趣的东西,请在评论中让我们知道!如果你喜欢这篇文章，或许你也会喜欢阅读“构建你的数据科学作品集”系列的其他文章：用数据讲故事如何搭建一个数据科学博客构建一个可以帮你找到工作的数据科学作品集的关键找到数据科学用的数据集的 17 个地方via: https://www.dataquest.io/blog/data-science-portfolio-machine-learning/作者：Vik Paruchuri 译者：kokialoves, zky001, vim-kakali, cposture, ideas4u 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出