构建你的数据科学作品集：用数据讲故事

Summary: 数据科学公司们在决定雇佣一个人时越来越看重其作品集。其中一个原因就是作品集是分析一个人真实技能的最好方式。

好长的文章，不过可以慢慢跟着做，要是有国内的数据源就好了

![](/data/attachment/album/201710/22/205831m4we17ipgm7e66us.jpg)

>
> 这是如何建立<ruby> 数据科学作品集 <rt>  Data Science Portfolio </rt></ruby>系列文章中的第一篇。如果你喜欢这篇文章并且想知道此系列的下一篇文章何时发表，你可以[在页面底部订阅](https://www.dataquest.io/blog/data-science-portfolio-project/#email-signup)。
>
>
>

数据科学公司们在决定雇佣一个人时越来越看重其作品集。其中一个原因就是<ruby> 作品集 <rt>  portfolio </rt></ruby>是分析一个人真实技能的最好方式。好消息是，作品集是完全可以被你掌控的。如果你在其上投入了一些工作，你就能够做出一个令那些公司印象深刻的作品集结果。

建立一个高质量作品集的第一步就是知道展示什么技能。那些公司们主要希望数据科学工作者拥有的技能，或者说他们主要希望作品集所展示的技能是：

* 表达能力
* 合作能力
* 专业技能
* 解释数据的能力
* 有目标和有积极性的

任何一个好的作品集都由多个工程构成，每一个工程都会展示 1-2 个上面所说的点。这是涵盖了“如何完成一个完整的数据科学作品集”系列文章的第一篇。在这篇文章中，我们将会涵括如何完成你的第一项数据科学作品集工程，并且对此进行有效的解释。在最后，你将会得到一个帮助展示你表达能力和解释数据能力的工程。

### 用数据讲故事

数据科学是表达的基础。你将会在数据中发现一些观点，并且找出一个高效的方式来向他人表达这些，之后向他们展示你所开展的课题。数据科学最关键的手法之一就是能够用数据讲述一个清晰的故事。一个清晰的故事能够使你的观点更加引人注目，并且能使别人理解你的想法。

数据科学中的故事是一个讲述你发现了什么，你怎么发现它的，并且它意味着什么的故事。例如假使发现你公司的收入相对去年减少了百分之二十。这并不能够确定原因，你可能需要和其它人沟通为什么收入会减少，并且在尝试修复它。

用数据讲故事主要包含：

* 理解并确定上下文
* 从多角度发掘
* 使用有趣的表示方法
* 使用多种数据来源
* 一致的表述

用来讲述数据的故事最有效率的工具就是 [Jupyter notebook](http://www.jupyter.org/)。如果你不熟悉，[此处](https://www.dataquest.io/blog/python-data-science/)有一个好的教程。Jupyter notebook 允许你交互式的发掘数据，并且将你的结果分享到多个网站，包括 Github。分享你的结果有助于合作研究和其他人拓展你的分析。

在这篇文章中，我们将使用 Jupyter notebook，以及 Pandas 和 matplotlib 这样的 Python 库。

### 为你的数据科学工程选择一个主题

建立一个工程的第一步就是决定你的主题。你要让你的主题是你兴趣所在，有动力去挖掘。进行数据挖掘时，为了完成而完成和有兴趣完成的区别是很明显的。这个步骤是值得花费时间的，所以确保你找到了你真正感兴趣的东西。

一个寻找主题的好方法就是浏览不同的数据集并且寻找感兴趣的部分。这里有一些作为起点的好的网站：

* [Data.gov](https://www.data.gov/) - 包含了政府数据。
* [/r/datasets](https://reddit.com/r/datasets) – 一个有着上百个有趣数据集的 reddit 板块。
* [Awesome datasets](https://github.com/caesar0301/awesome-public-datasets) – 一个数据集的列表，位于 Github 上。
* [17 个找到数据集的地方](https://www.dataquest.io/blog/free-datasets-for-projects/) – 这篇博文列出了 17 个数据集，每个都包含了示例数据集。

真实世界中的数据科学，你经常无法找到可以浏览的合适的单个数据集。你可能需要聚合多个独立的数据源，或者做数量庞大的数据清理。如果该主题非常吸引你，这是值得这样做的，并且也能更好的展示你的技能。

关于这篇文章的主题，我们将使用纽约市公立学校的数据，我们可以在[这里](https://data.cityofnewyork.us/data?cat=education)找到它。

### 选择主题

这对于项目全程来说是十分重要的。因为主题能很好的限制项目的范围，并且它能够使我们知道它可以被完成。比起一个没有足够动力完成的工程来说，添加到一个完成的工程更加容易。

所以，我们将关注高中的[学术评估测试](https://en.wikipedia.org/wiki/SAT)，伴随着多种人口统计和它们的其它数据。关于学习评估测试， 或者说 SAT，是美国高中生申请大学前的测试。大学在做判定时将考虑该成绩，所以高分是十分重要的。考试分为三个阶段，每个阶段总分为 800。全部分数为 2400（即使这个前后更改了几次，在数据中总分还是 2400）。高中经常通过平均 SAT分数进行排名，并且 SAT 是评判高中有多好的标准。

因为由关于 SAT 分数对于美国中某些种族群体是不公平的，所以对纽约市这个数据做分析能够对 SAT 的公平性有些许帮助。

我们在[这里](https://data.cityofnewyork.us/Education/SAT-Results/f9bf-2cp4)有 SAT 成绩的数据集，并且在[这里](https://data.cityofnewyork.us/Education/DOE-High-School-Directory-2014-2015/n3p6-zve2)有包含了每所高中的信息的数据集。这些将构成我们的工程的基础，但是我们将加入更多的信息来创建有趣的分析。

### 补充数据

如果你已经有了一个很好的主题，拓展其它可以提升主题或者更深入挖掘数据的的数据集是一个好的选择。在前期十分适合做这些工作，你将会有尽可能多的数据来构建你的工程。数据越少意味着你会太早的放弃了你的工程。

在本项目中，在包含人口统计信息和测试成绩的网站上有一些相关的数据集。

这些是我们将会用到的所有数据集：

* [学校 SAT 成绩](https://data.cityofnewyork.us/Education/SAT-Results/f9bf-2cp4) – 纽约市每所高中的 SAT 成绩。
* [学校出勤情况](https://data.cityofnewyork.us/Education/School-Attendance-and-Enrollment-Statistics-by-Dis/7z8d-msnt) – 纽约市每所学校的出勤信息。
* [数学成绩](https://data.cityofnewyork.us/Education/NYS-Math-Test-Results-By-Grade-2006-2011-School-Le/jufi-gzgp) – 纽约市每所学校的数学成绩。
* [班级规模](https://data.cityofnewyork.us/Education/2010-2011-Class-Size-School-level-detail/urz7-pzb3) - 纽约市每所学校课堂人数信息。
* [AP 成绩](https://data.cityofnewyork.us/Education/AP-College-Board-2010-School-Level-Results/itfs-ms3e) - 高阶位考试，在美国，通过 AP 测试就能获得大学学分。
* [毕业去向](https://data.cityofnewyork.us/Education/Graduation-Outcomes-Classes-Of-2005-2010-School-Le/vh2h-md7a) – 由百分之几的学生毕业了，和其它去向信息。
* [人口统计](https://data.cityofnewyork.us/Education/School-Demographics-and-Accountability-Snapshot-20/ihfw-zy9j) – 每个学校的人口统计信息。
* [学校问卷](https://data.cityofnewyork.us/Education/NYC-School-Survey-2011/mnz3-dyi8) – 学校的家长、教师，学生的问卷。
* [学校分布地图](https://data.cityofnewyork.us/Education/School-Districts/r8nu-ymqj) – 包含学校的区域布局信息，因此我们能将它们在地图上标出。

（LCTT 译注：高阶位考试（AP）是美国和加拿大的一个由大学委员会创建的计划，该计划为高中学生提供大学水平的课程和考试。 美国学院和大学可以授予在考试中获得高分的学生的就学和课程学分。）

这些数据作品集之间是相互关联的，并且我们能够在开始分析之前进行合并。

### 获取背景信息

在开始分析数据之前，搜索一些背景信息是有必要的。我们知道这些有用的信息：

* 纽约市被分为五个不同的辖区
* 纽约市的学校被分配到几个学区，每个学区都可能包含数十所学校。
* 数据集中的学校并不全是高中，所以我们需要对数据进行一些清理工作。
* 纽约市的每所学校都有自己单独的编码，被称为‘DBN’，即区域行政编号。
* 为了通过区域进行数据聚合，我们可以使用地图区域信息来绘制逐区差异。

### 理解数据

为了真正的理解数据信息，你需要花费时间来挖掘和阅读数据。因此，每个数据链接都有数据的描述信息，并伴随着相关列。就像是我们拥有的高中 SAT 成绩信息，也包含图像和其它信息的数据集。

我们可以运行一些代码来读取数据。我们将使用 [Jupyter notebook](http://jupyter.org/) 来挖掘数据。下面的代码将会执行以下操作：

* 循环遍历我们下载的所有数据文件。
* 将文件读取到 [Pandas DataFrame](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.html)。
* 将所有数据框架导入 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](https://www.dataquest.io/blog/data-science-portfolio-project/) 方法打印每个 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`，我们[在此](https://developer.cityofnewyork.us/api/doe-school-choice)得到了结果。它解释了 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]:

|  | CSD | BOROUGH | SCHOOL CODE | SCHOOL NAME | GRADE | PROGRAM TYPE | CORE SUBJECT (MS CORE and 9-12 ONLY) | CORE COURSE (MS CORE and 9-12 ONLY) | SERVICE CATEGORY(K-9\* ONLY) | NUMBER OF STUDENTS / SEATS FILLED | NUMBER OF SECTIONS | AVERAGE CLASS SIZE | SIZE OF SMALLEST CLASS | SIZE OF LARGEST CLASS | DATA SOURCE | SCHOOLWIDE PUPIL-TEACHER RATIO |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | GEN ED | - | - | - | 19.0 | 1.0 | 19.0 | 19.0 | 19.0 | ATS | NaN |
| 1 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | CTT | - | - | - | 21.0 | 1.0 | 21.0 | 21.0 | 21.0 | ATS | NaN |
| 2 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | GEN ED | - | - | - | 17.0 | 1.0 | 17.0 | 17.0 | 17.0 | ATS | NaN |
| 3 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | CTT | - | - | - | 17.0 | 1.0 | 17.0 | 17.0 | 17.0 | ATS | NaN |
| 4 | 1 | M | M015 | P.S. 015 Roberto Clemente | 02 | GEN ED | - | - | - | 15.0 | 1.0 | 15.0 | 15.0 | 15.0 | ATS | NaN |

正如上面所见，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](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.concat.html) 方法将数据集合并为一个。

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\_p | N\_s | N\_t | aca*p*11 | aca*s*11 | aca*t*11 | aca*tot*11 | bn | com*p*11 | com*s*11 | ... | t*q8c*1 | t*q8c*2 | t*q8c*3 | t*q8c*4 | t\_q9 | t*q9*1 | t*q9*2 | t*q9*3 | t*q9*4 | t*q9*5 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 90.0 | NaN | 22.0 | 7.8 | NaN | 7.9 | 7.9 | M015 | 7.6 | NaN | ... | 29.0 | 67.0 | 5.0 | 0.0 | NaN | 5.0 | 14.0 | 52.0 | 24.0 | 5.0 |
| 1 | 161.0 | NaN | 34.0 | 7.8 | NaN | 9.1 | 8.4 | M019 | 7.6 | NaN | ... | 74.0 | 21.0 | 6.0 | 0.0 | NaN | 3.0 | 6.0 | 3.0 | 78.0 | 9.0 |
| 2 | 367.0 | NaN | 42.0 | 8.6 | NaN | 7.5 | 8.0 | M020 | 8.3 | NaN | ... | 33.0 | 35.0 | 20.0 | 13.0 | NaN | 3.0 | 5.0 | 16.0 | 70.0 | 5.0 |
| 3 | 151.0 | 145.0 | 29.0 | 8.5 | 7.4 | 7.8 | 7.9 | M034 | 8.2 | 5.9 | ... | 21.0 | 45.0 | 28.0 | 7.0 | NaN | 0.0 | 18.0 | 32.0 | 39.0 | 11.0 |
| 4 | 90.0 | NaN | 23.0 | 7.9 | NaN | 8.1 | 8.0 | M063 | 7.9 | NaN | ... | 59.0 | 36.0 | 5.0 | 0.0 | NaN | 10.0 | 5.0 | 10.0 | 60.0 | 15.0 |

5 rows × 2773 columns

我们可以通过查看数据文件夹中伴随问卷数据下载下来的文件来解决这个问题。它告诉我们们数据中重要的部分是哪些：

![](/data/attachment/album/201710/22/210107mcffn6xt88xfxxtu.png)

我们可以去除 `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]:

|  | CSD | BOROUGH | SCHOOL CODE | SCHOOL NAME | GRADE | PROGRAM TYPE | CORE SUBJECT (MS CORE and 9-12 ONLY) | CORE COURSE (MS CORE and 9-12 ONLY) | SERVICE CATEGORY(K-9\* ONLY) | NUMBER OF STUDENTS / SEATS FILLED | NUMBER OF SECTIONS | AVERAGE CLASS SIZE | SIZE OF SMALLEST CLASS | SIZE OF LARGEST CLASS | DATA SOURCE | SCHOOLWIDE PUPIL-TEACHER RATIO | DBN |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 0 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | GEN ED | - | - | - | 19.0 | 1.0 | 19.0 | 19.0 | 19.0 | ATS | NaN | 01M015 |
| 1 | 1 | M | M015 | P.S. 015 Roberto Clemente | 0K | CTT | - | - | - | 21.0 | 1.0 | 21.0 | 21.0 | 21.0 | ATS | NaN | 01M015 |
| 2 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | GEN ED | - | - | - | 17.0 | 1.0 | 17.0 | 17.0 | 17.0 | ATS | NaN | 01M015 |
| 3 | 1 | M | M015 | P.S. 015 Roberto Clemente | 01 | CTT | - | - | - | 17.0 | 1.0 | 17.0 | 17.0 | 17.0 | ATS | NaN | 01M015 |
| 4 | 1 | M | M015 | P.S. 015 Roberto Clemente | 02 | GEN ED | - | - | - | 15.0 | 1.0 | 15.0 | 15.0 | 15.0 | ATS | NaN | 01M015 |

每所高中都有许多行（正如你所见的重复的 `DBN` 和 `SCHOOL NAME`）。然而，如果我们看向 `sat_result` 数据集，每所高中只有一行：

In [21]:

```
data["sat_results"].head()

```

Out[21]:

|  | DBN | SCHOOL NAME | Num of SAT Test Takers | SAT Critical Reading Avg. Score | SAT Math Avg. Score | SAT Writing Avg. Score |
| --- | --- | --- | --- | --- | --- | --- |
| 0 | 01M292 | HENRY STREET SCHOOL FOR INTERNATIONAL STUDIES | 29 | 355 | 404 | 363 |
| 1 | 01M448 | UNIVERSITY NEIGHBORHOOD HIGH SCHOOL | 91 | 383 | 423 | 366 |
| 2 | 01M450 | EAST SIDE COMMUNITY SCHOOL | 70 | 377 | 402 | 370 |
| 3 | 01M458 | FORSYTH SATELLITE ACADEMY | 7 | 414 | 401 | 359 |
| 4 | 01M509 | MARTA VALLE HIGH SCHOOL | 44 | 390 | 433 | 384 |

为了合并这些数据集，我们将需要找到方法将数据集精简到如 `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` 方法来合并缺少行的数据集，这样我们就不会丢失数据。在实际分析中，缺少数据是很常见的。能够展示解释和解决数据缺失的能力是构建一个作品集的重要部分。

你可以在[此](http://pandas.pydata.org/pandas-docs/stable/merging.html)阅读关于不同类型的合并。

接下来的代码，我们将会：

* 循环遍历 `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](https://apstudent.collegeboard.org/home) 考试结果与 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](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.corr.html) 方法来完成。越接近 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]:

![](/data/attachment/album/201710/22/210109yzwnyr56y9wn18nb.png)

这个地图十分有用，但是不容易查看纽约哪里学校最多。因此，我们将用热力图来代替它：

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]:

![](/data/attachment/album/201710/22/210112ujzoji0x0qtthqmb.png)

### 区域级别映射

热力图能够很好的标出梯度，但是我们将需要更结构化的画出不同城市之间的 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](http://geojson.org/) 中的数据，转化为每个区域的形状，然后通过 `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]:

![](/data/attachment/album/201710/22/210114q8h887kp6h1a0f0d.png)

### 挖掘注册学生数与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>

```

![](/data/attachment/album/201710/22/205221xdmq42xybbyxxcjg.jpg)

如你所见，底下角注册人数较低的部分有个较低 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>

```

![](/data/attachment/album/201710/22/205251usn0lysp8lnnt8ml.jpg)

看起来这里有一组学校有着高的 `ell_percentage` 值并且有着低的 SAT 成绩。我们可以在学区层面调查这个关系，通过找出每个学区英语学习者所占的比例，并且查看是否与我们的学区层面的 SAT 地图所匹配：

In [90]:

```
show_district_map("ell_percent")

```

Out[90]:

![](/data/attachment/album/201710/22/210116ggxdlddgh88dhcll.png)

我们可通过两个区域层面地图来查看，一个低 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>

```

![](/data/attachment/album/201710/22/205319o3o3ofl6xknayyyj.jpg)

惊人的是，关联最大的两个因子是 `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>

```

![](/data/attachment/album/201710/22/205344iiah3s2gyh0n4i8i.jpg)

看起来更高比例的白种和亚洲学生与更高的 SAT 分数有关联，而更高比例的黑人和西班牙裔与更低的 SAT 分数有关联。对于西班牙学生，这可能因为近年的移民还是英语学习者的事实。我们可以标出学区层面的西班牙裔的比例并观察联系。

In [93]:

```
show_district_map("hispanic_per")

```

Out[93]:

![](/data/attachment/album/201710/22/210118co5xdtccihtcoid6.png)

看起来这里与英语学习者比例有关联，但是有必要对这种和其它种族在 SAT 分数上的差异进行挖掘。

### SAT 分数上的性别差异

挖掘性别与 SAT 分数之间的关系是最后一个角度。我们注意更高的女生比例的学校倾向于与更高的 SAT 分数有关联。我们可以可视化为一个条形图：

In [94]:

```
full.corr()["sat_score"][["male_per", "female_per"]].plot.bar()

```

Out[94]:

```
<matplotlib.axes._subplots.AxesSubplot at 0x10774d0f0>

```

![](/data/attachment/album/201710/22/205412svzwa7rt17w9a1v7.jpg)

为了挖掘更多的关联性，我们可以制作一个 `female_per` 和 `sat_score` 的散点图：

In [95]:

```
full.plot.scatter(x='female_per', y='sat_score')

```

Out[95]:

```
<matplotlib.axes._subplots.AxesSubplot at 0x104715160>

```

![](/data/attachment/album/201710/22/205435dvevmflfnmf2dn1d.jpg)

看起来这里有一个高女生比例、高 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>

```

![](/data/attachment/album/201710/22/205510gvev33irla3efirf.jpg)

看起来它们之间确实有着很强的关联。有趣的是右上角高 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](https://github.com/) 上是一个好的想法，这样别人就能够与你一起合作。

如果你喜欢这篇文章，你可能希望阅读我们‘Build a Data Science Portfolio’系列文章的其它部分：

* [如何搭建一个数据科学博客](https://www.dataquest.io/blog/how-to-setup-a-data-science-blog/)
* [建立一个机器学习工程](/article-7907-1.html)
* [构建一个将帮助你找到工作的数据科学作品集的关键](https://www.dataquest.io/blog/build-a-data-science-portfolio/)
* [17 个你能找到其它数据科学工程数据集的地方](https://www.dataquest.io/blog/free-datasets-for-projects)
* [怎样在 GitHub 上展示你的数据科学作品集](https://www.dataquest.io/blog/how-to-share-data-science-portfolio/)

---

via: <https://www.dataquest.io/blog/data-science-portfolio-project/>

作者：[Vik Paruchuri](http://twitter.com/vikparuchuri)  译者：[Yoo-4x] 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Linus Torvalds 说针对性的模糊测试正提升 Linux 安全性

Linux 4.14 发布候选第五版已经出来。Linus Torvalds 说：“可以去测试了。”

![linus-toravlds-linuxcon-toronto.jpg](/data/attachment/album/201710/23/144908tmjc3qpntw11xtpz.jpg)

随着宣布推出 Linux 内核 4.14 的第五个候选版本，Linus Torvalds 表示<ruby> 模糊测试 <rt>  fuzzing </rt></ruby>正产生一系列稳定的安全更新。

模糊测试通过产生随机代码来引发错误来对系统进行压力测试，从而有助于识别潜在的安全漏洞。模糊测试可以帮助软件开发人员在向用户发布软件之前捕获错误。

Google 使用各种模糊测试工具来查找它及其它供应商软件中的错误。微软推出了 [Project Springfield](http://www.zdnet.com/article/microsoft-seeks-testers-for-project-springfield-bug-detection-service/) 模糊测试服务，它能让企业客户测试自己的软件。

正如 Torvalds 指出的那样，Linux 内核开发人员从一开始就一直在使用模糊测试流程，例如 1991 年发布的工具 “crashme”，它在近 20 年后被 [Google 安全研究员 Tavis Ormandy](http://taviso.decsystem.org/virtsec.pdf)  用来测试在虚拟机中处理不受信任的数据时，宿主机是否受到良好保护。

Torvalds [说](http://lkml.iu.edu/hypermail/linux/kernel/1710.1/06454.html)：“另外值得一提的是人们做了多少随机化模糊测试，而且这正在发现东西。”

“我们一直在做模糊测试（谁还记得只是生成随机代码，并跳转过去的老 “crashme” 程序？我们过去很早就这样做），人们在驱动子系统等方面做了一些很好的针对性模糊测试，而且已经有了各种各样的修复（不仅仅是上周的这些）。很高兴可以看到。”

Torvalds 提到，到目前为止，4.14 的发展“比预想的要麻烦一些”，但现在已经好了，并且在这个版本已经跑通了一些针对 x86 系统以及 AMD 芯片系统的修复。还有几个驱动程序、核心内核组件和工具的更新。

如前[所述](http://www.zdnet.com/article/first-linux-4-14-release-adds-very-core-features-arrives-in-time-for-kernels-26th-birthday/)，Linux 4.14 是 2017 年的长期稳定版本，迄今为止，它引入了核心内存管理功能、设备驱动程序更新以及文档、架构、文件系统、网络和工具的修改。

---

via: <http://www.zdnet.com/article/linus-torvalds-says-targeted-fuzzing-is-improving-linux-security/>

作者：[Liam Tung](http://www.zdnet.com/meet-the-team/eu/liam-tung/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


极客漫画：谁杀死了 MySQL

Summary: 在这里 MySQL 意外被 SIGKILL 杀死了，没有留下任何遗言和痕迹（日志）。

那到底是谁杀死了Mysql呢？

![who-killed-mysql](/data/attachment/album/201710/23/164740ha3o4899iai7qg98.png)

LAMP 架构，指 Linux + Apache + MySQL + PHP 组合构成的一个完整的 Web 服务架构。这是一个经典而有点过时的架构，适合于小型的 Web 服务。

在这里 MySQL 意外被 [SIGKILL](/article-8791-1.html) [杀死](/article-8771-1.html)了，没有留下任何遗言和痕迹（日志）。

---

via: <http://turnoff.us/geek/who-killed-mysql/>

作者：[Daniel Stori](http://turnoff.us/about/) 译者：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


回顾 Steam Machines 与 SteamOS

![](/data/attachment/album/201710/23/183038dcylwi4spln99qyq.jpg)

去年今日（LCTT 译注：本文发表于 2016 年），在非常符合 Valve 风格的跳票之后，大众迎来了 [Steam Machines 的发布](https://www.gamingonlinux.com/articles/steam-machines-steam-link-steam-controller-officially-released-steamos-sale.6201)。即使是在 Linux 桌面环境对于游戏的支持大步进步的今天，Steam Machines 作为一个平台依然没有飞跃，而 SteamOS 似乎也止步不前。这些由 Valve 发起的项目究竟怎么了？这些项目为何被发起，又是如何失败的？一些改进又是否曾有机会挽救这些项目的成败？

### 行业环境

在 2012 年 Windows 8 发布的时候，微软像 iOS 与 Android 那样，为 Windows 集成了一个应用商店。在微软试图推广对触摸体验友好的界面时，为了更好的提供 “Metro” UI 语言指导下的沉浸式触摸体验，他们同时推出了一系列叫做 “WinRT” 的 API。然而为了能够使用这套 API，应用开发者们必须把应用程序通过 Windows 应用商城发布，并且正如其它应用商城那样，微软从中抽成 30%。对于 Valve 的 CEO，Gabe Newell (G 胖) 而言，这种限制发布平台和抽成行为是让人无法接受的，而且他前瞻地看到了微软利用行业龙头地位来推广 Windows 商店和 Metro 应用对于 Valve 潜在的危险，正如当年微软用 IE 浏览器击垮 Netscape 浏览器一样。

对于 Valve 来说，运行 Windows 的 PC 的优势在于任何人都可以不受操作系统和硬件方的限制运行各种软件。当像 Windows 这样的专有平台对像 Steam 这样的第三方软件限制越来越严格时，应用开发者们自然会想要寻找一个对任何人都更开放和自由的替代品，他们很自然的会想到 Linux 。Linux 本质上只是一套内核，但你可以轻易地使用 GNU 组件、Gnome 等软件在这套内核上开发出一个操作系统，比如 Ubuntu 就是这么来的。推行 Ubuntu 或者其他 Linux 发行版自然可以为 Valve 提供一个无拘无束的平台，以防止微软或者苹果变成 Valve 作为第三方平台之路上的的敌人，但 Linux 甚至给了 Valve 一个创造新的操作系统平台的机会。

### 概念化

如果我们把 Steam Machines 叫做主机的话，Valve 当时似乎认定了主机平台是一个机会。为了迎合用户对于电视主机平台用户界面的审美期待，同时也为了让玩家更好地从稍远的距离上在电视上玩游戏，Valve 为 Steam 推出了 Big Picture 模式。Steam Machines 的核心要点是开放性；比方说所有的软件都被设计成可以脱离 Windows 工作，又比如说 Steam Machines 手柄的 CAD 图纸也被公布出来以便支持玩家二次创作。

原初计划中，Valve 打算设计一款官方的 Steam Machine 作为旗舰机型。但最终，这些机型只在 2013 年的时候作为原型机给与了部分测试者用于测试。Valve 后来也允许像戴尔这样的 OEM 厂商们制造 Steam Machines，并且也赋予了他们制定价格和配置规格的权利。有一家叫做 “Xi3” 的公司展示了他们设计的 Steam Machine 小型机型，那款机型小到可以放在手掌上，这一新闻创造了围绕 Steam Machines 的更多热烈讨论。最终，Valve 决定不自己设计制造 Steam Machines，而全权交给 OEM 合作厂商们。

这一过程中还有很多天马行空的创意被列入考量，比如在手柄上加入生物识别技术、眼球追踪以及动作控制等。在这些最初的想法里，陀螺仪被加入了 Steam Controller 手柄，HTC Vive 的手柄也有各种动作追踪仪器；这些想法可能最初都来源于 Steam 手柄的设计过程中。手柄最初还有些更激进的设计，比如在中心放置一块可定制化并且会随着游戏内容变化的触摸屏。但最后的最后，发布会上的手柄偏向保守了许多，但也有诸如双触摸板和内置软件等黑科技。Valve 也考虑过制作面向笔记本类型硬件的 Steam Machines 和 SteamOS。这个企划最终没有任何成果，但也许 “Smach Z” 手持游戏机会是发展的方向之一。

在 [2013 年九月](https://www.gamingonlinux.com/articles/valve-announces-steam-machines-you-can-win-one-too.2469)，Valve 对外界宣布了 Steam Machines 和 SteamOS， 并且预告会在 2014 年中发布。前述的 300 台原型机在当年 12 月分发给了测试者们，随后次年 1 月，又分发给了开发者们 2000 台原型机。SteamOS 也在那段时间分发给有 Linux 经验的测试者们试用。根据当时的测试反馈，Valve 最终决定把产品发布延期到 2015 年 11 月。

SteamOS 的延期跳票给合作伙伴带来了问题；戴尔的 Steam Machine 由于早发售了一年，结果不得不改为搭配了额外软件、甚至运行着 Windows 操作系统的 Alienware Alpha。

### 正式发布

在最终的正式发布会上，Valve 和 OEM 合作商们发布了 Steam Machines，同时 Valve 还推出了 Steam Controller 手柄和 Steam Link 串流游戏设备。Valve 也在线下零售行业比如 GameStop 里开辟了货架空间。在发布会前，有几家 OEM 合作商退出了与 Valve 的合作；比如 Origin PC 和 Falcon Northwest 这两家高端精品主机设计商。他们宣称 Steam 生态的性能问题和一些限制迫使他们决定弃用 SteamOS。

Steam Machines 在发布后收到了褒贬不一的评价。另一方面 Steam Link 则普遍受到好评，很多人表示愿意在客厅电视旁为他们已有的 PC 系统购买 Steam Link， 而不是购置一台全新的 Steam Machine。Steam Controller 手柄则受到其丰富功能伴随而来的陡峭学习曲线影响，评价一败涂地。然而针对 Steam Machines 的批评则是最猛烈的。诸如 LinusTechTips 这样的评测团体 （LCTT 译注：YouTube 硬件界老大，个人也经常看他们节目）注意到了主机的明显的不足，其中甚至不乏性能为题。很多厂商的 Machines 都被批评为性价比极低，特别是经过和玩家们自己组装的同配置机器或者电视主机做对比之后。SteamOS 而被批评为兼容性有问题，Bug 太多，以及性能不及 Windows。在所有 Machines 里，戴尔的 Alienware Alpha 被评价为最有意思的一款，主要是由于品牌价值和机型外观极小的缘故。

通过把 Debian Linux 操作系统作为开发基础，Valve 得以为 SteamOS 平台找到很多原本就存在与 Steam 平台上的 Linux 兼容游戏来作为“首发游戏”。所以起初大家认为在“首发游戏”上 Steam Machines 对比其他新发布的主机优势明显。然而，很多宣称会在新平台上发布的游戏要么跳票要么被中断了。Rocket League 和 Mad Max 在宣布支持新平台整整一年后才真正发布，而《巫师 3》和《蝙蝠侠：阿克汉姆骑士》甚至从来没有发布在新平台上。就《巫师 3》的情况而言，他们的开发者 CD Projekt Red 拒绝承认他们曾经说过要支持新平台；然而他们的游戏曾在宣布支持 Linux 和 SteamOS 的游戏列表里赫然醒目。雪上加霜的是，很多 AAA 级的大作甚至没宣布移植，虽然最近这种情况稍有所好转了。

### 被忽视的

在 Stame Machines 发售后，Valve 的开发者们很快转移到了其他项目的工作中去了。在当时，VR 项目最为内部所重视，6 月份的时候大约有 1/3 的员工都在相关项目上工作。Valve 把 VR 视为亟待开发的一片领域，而他们的 Steam 则应该作为分发 VR 内容的生态环境。通过与 HTC 合作生产，Valve 设计并制造出了他们自己的 VR 头戴和手柄，并计划在将来更新换代。然而与此同时，Linux 和 Steam Machines 都渐渐淡出了视野。SteamVR 甚至直到最近才刚刚支持 Linux （其实还没对普通消费者开放使用，只在 SteamDevDays 上展示过对 Linux 的支持），而这一点则让我们怀疑 Valve 在 Stame Machines 和 Linux 的开发上是否下定了足够的决心。

SteamOS 自发布以来几乎止步不前。SteamOS 2.0 作为上一个大版本号更新，几乎只是同步了 Debian 上游的变化，而且还需要用户重新安装整个系统，而之后的小补丁也只是在做些上游更新的配合。当 Valve 在其他事关性能和用户体验的项目（例如 Mesa）上进步匪浅的时候，针对 Steam Machines 的相关项目则少有顾及。

很多原本应有的功能都从未完成。Steam 的内置功能，例如聊天和直播，都依然处于较弱的状态，而且这种落后会影响所有平台上的 Steam 用户体验。更具体来说，Steam 没有像其他主流主机平台一样把诸如 Netflix、Twitch 和 Spotify 之类的服务集成到客户端里，而通过 Steam 内置的浏览器使用这些服务则体验极差，甚至无法使用；而如果要使用第三方软件则需要开启 Terminal，而且很多软件甚至无法支持控制手柄 —— 无论从哪方面讲这样的用户界面体验都糟糕透顶。

Valve 同时也几乎没有花任何力气去推广他们的新平台，而选择把一切都交由 OEM 厂商们去做。然而，几乎所有 OEM 合作商们要么是高端主机定制商，要么是电脑生产商，要么是廉价电脑公司（LCTT 译注：简而言之没有一家有大型宣传渠道）。在所有 OEM 中，只有戴尔是 PC 市场的大碗，也只有他们真正给 Steam Machines 做了广告宣传。

最终销量也不尽人意。截至 2016 年 6 月，7 个月间 Steam Controller 手柄的销量在包括捆绑销售的情况下仅销售 500,000 件。这让 Steam Machines 的零售情况差到只能被归类到十万俱乐部的最底部。对比已经存在的巨大 PC 和主机游戏平台，可以说销量极低。

### 事后诸葛亮

既然知道了 Steam Machines 的历史，我们又能否总结出失败的原因以及可能存在的翻身改进呢？

#### 视野与目标

Steam Machines 从来没搞清楚他们在市场里的定位究竟是什么，也从来没说清楚他们具体有何优势。从 PC 市场的角度来说，自己搭建台式机已经非常普及，并且往往可以让电脑可以匹配玩家自己的目标，同时升级性也非常好。从主机平台的角度来说，Steam Machines 又被主机本身的相对廉价所打败，虽然算上游戏可能稍微便宜一些，但主机上的用户体验也直白很多。

PC 用户会把多功能性看得很重，他们不仅能用电脑打游戏，也一样能办公和做各种各样的事情。即使 Steam Machines 也是跑着的 SteamOS 操作系统的自由的 Linux 电脑，但操作系统和市场宣传加深了 PC 玩家们对 Steam Machines 是不可定制的硬件、低价的、更接近主机的印象。即使这些 PC 用户能接受在客厅里购置一台 Steam Machines，他们也有 Steam Link 可以选择，而且很多更小型机比如 NUC 和 Mini-ITX 主板定制机可以让他们搭建更适合放在客厅里的电脑。SteamOS 软件也允许把这些硬件转变为 Steam Machines，但寻求灵活性和兼容性的用户通常都会使用一般 Linux 发行版或者 Windows。何况最近的 Windows 和 Linux 桌面环境都让维护一般用户的操作系统变得自动化和简单了。

电视主机用户们则把易用性放在第一。虽然近年来主机的功能也逐渐扩展，比如可以播放视频或者串流，但总体而言用户还是把即插即用即玩、不用担心兼容性和性能问题和低门槛放在第一。主机的使用寿命也往往较长，一般在 4-7 年左右，而统一固定的硬件也让游戏开发者们能针对其更好的优化和调试软件。现在刚刚兴起的中生代升级，例如天蝎和 PS 4 Pro 则可能会打破这样统一的游戏体验，但无论如何厂商还是会要求开发者们需要保证游戏在原机型上的体验。为了提高用户粘性，主机也会有自己的社交系统和独占游戏。而主机上的游戏也有实体版，以便将来重用或者二手转卖，这对零售商和用户都是好事儿。Steam Machines 则完全没有这方面的保证；即使长的像一台客厅主机，他们却有 PC 高昂的价格和复杂的硬件情况。

#### 妥协

综上所述，Steam Machines 可以说是“集大成者”，吸取了两边的缺点，又没有自己明确的定位。更糟糕的是 Steam Machines 还展现了 PC 和主机都没有的毛病，比如没有 AAA 大作，又没有 Netflix 这样的客户端。抛开这些不说，Valve 在提高他们产品这件事上几乎没有出力，甚至没有尝试着解决 PC 和主机两头定位矛盾这一点。

然而在有些事情上也许原本 PC 和主机就没法折中妥协。像图像设定和 Mod 等内容的加入会无法保证“傻瓜机”一般的可靠，而且系统下层的复杂性也会逐渐暴露。

而最复杂的是 Steam Machines 多变的硬件情况，这使得用户不仅要考虑价格还要考虑配置，还要考虑这个价格下和别的系统（PC 和主机）比起来划算与否。更关键的是，Valve 无论如何也应该做出某种自动硬件检测机制，这样玩家才能知道是否能玩某个游戏，而且这个测试既得简单明了，又要能支持 Steam 上几乎所有游戏。同时，Valve 还要操心未来游戏对配置需求的变化，比如2016 年的 "A" 等主机三年后该给什么评分呢？

#### Valve, 个人努力与公司结构

尽管 Valve 在 Steam 上创造了辉煌，但其公司的内部结构可能对于开发一个像 Steam Machines 一样的平台是有害的。他们几乎没有领导的自由办公结构，以及所有人都可以自由移动到想要工作的项目组里决定了他们具有极大的创新，研发，甚至开发能力。据说 Valve 只愿意招他们眼中的的 “顶尖人才”，通过极其严格的筛选标准，并通过让他们在自己认为“有意义”的项目里工作以保持热情。然而这种思路很可能是错误的；拉帮结派总是存在，而 G胖的话或许比公司手册上写的还管用，而又有人时不时会由于特殊原因被雇佣或解雇。

正因为如此，很多虽不闪闪发光甚至维护起来有些无聊但又需要大量时间的项目很容易枯萎。Valve 的客服已是被人诟病已久的毛病，玩家经常觉得被无视了，而 Valve 则经常不到万不得已、法律要求的情况下绝不行动：例如自动退款系统，就是在澳大利亚和欧盟法律的要求下才被加入的；更有目前还没结案的华盛顿州 CS:GO 物品在线赌博网站一案。

各种因素最后也反映在 Steam Machines 这一项目上。Valve 方面的跳票迫使一些合作方做出了尴尬的决定，比如戴尔提前一年发布了 Alienware Alpha 外观的 Steam Machine 就在一年后的正式发布时显得硬件状况落后了。跳票很可能也导致了游戏数量上的问题。开发者和硬件合作商的对跳票和最终毫无轰动的发布也不明朗。Valve 的 VR 平台干脆直接不支持 Linux，而直到最近，SteamVR 都风风火火迭代了好几次之后，SteamOS 和 Linux 依然不支持 VR。

#### “长线钓鱼”

尽管 Valve 方面对未来的规划毫无透露，有些人依然认为 Valve 在 Steam Machine 和 SteamOS 上是放长线钓大鱼。他们论点是 Steam 本身也是这样的项目 —— 一开始作为游戏补丁平台出现，到现在无敌的游戏零售和玩家社交网络。虽然 Valve 的独占游戏比如《半条命 2》和 《CS》 也帮助了 Steam 平台的传播。但现今我们完全无法看到 Valve 像当初对 Steam 那样上心 Steam Machines。同时现在 Steam Machines 也面临着 Steam 从没碰到过的激烈竞争。而这些竞争里自然也包含 Valve 自己的那些把 Windows 作为平台的 Steam 客户端。

#### 真正目的

鉴于投入在 Steam Machines 上的努力如此之少，有些人怀疑整个产品平台是不是仅仅作为某种博弈的筹码才被开发出来。原初 Steam Machines 就发家于担心微软和苹果通过自己的应用市场垄断游戏的反制手段当中，Valve 寄希望于 Steam Machines 可以在不备之时脱离那些操作系统的支持而运行，同时也是提醒开发者们，也许有一日整个 Steam 平台会独立出来。而当微软和苹果等方面的风口没有继续收紧的情况下，Valve 自然就放慢了开发进度。然而我不这样认为；Valve 其实已经花了不少精力与硬件商和游戏开发者们共同推行这件事，不可能仅仅是为了吓吓他人就终止项目。你可以把这件事想成，微软和 Valve 都在吓唬对方 —— 微软推出了突然收紧的 Windows 8 ，而 Valve 则展示了一下可以独立门户的能力。

但即使如此，谁能保证开发者不会愿意跟着微软的封闭环境跑了呢？万一微软最后能提供更好的待遇和用户群体呢？更何况，微软现在正大力推行 Xbox 和 Windows 的交叉和整合，甚至 Xbox 独占游戏也出现在 Windows 上，这一切都没有损害 Windows 原本的平台性定位 —— 谁还能说微软方面不是 Steam 的直接竞争对手呢？

还会有人说这一切一切都是为了推进 Linux 生态环境尽快接纳 PC 游戏，而 Steam Machines 只是想为此大力推一把。但如果是这样，那这个目的实在是性价比极低，因为本愿意支持 Linux 的自然会开发，而 Steam Machines 这一出甚至会让开发者对平台期待额落空从而伤害到他们。

### 大家眼中 Valve 曾经的机会

我认为 Steam Machines 的创意还是很有趣的，而也有一个与之匹配的市场，但就结果而言 Valve 投入的创意和努力还不够多，而定位模糊也伤害了这个产品。我认为 Steam Machines 的优势在于能砍掉 PC 游戏传统的复杂性，比如硬件问题、整机寿命和维护等；但又能拥有游戏便宜，可以打 Mod 等好处，而且也可以做各种定制化以满足用户需求。但他们必须要让产品的核心内容：价格、市场营销、机型产品线还有软件的质量有所保证才行。

我认为 Steam Machines 可以做出一点妥协，比如硬件升级性（尽管这一点还是有可能被保留下来的 —— 但也要极为小心整个过程对用户体验的影响）和产品选择性，来减少摩擦成本。PC 一直会是一个并列的选项。想给用户产品可选性带来的只有一个困境，成吨的质量低下的 Steam Machines 根本不能解决。Valve 得自己造一台旗舰机型来指明 Steam Machines 的方向。毫无疑问，Alienware 的产品是最接近理想目标的，但它说到底也不是 Valve 的官方之作。Valve 内部不乏优秀的工业设计人才，如果他们愿意投入足够多的重视，我认为结果也许会值得他们努力。而像戴尔和 HTC 这样的公司则可以用他们丰富的经验帮 Valve 制造成品。直接钦定 Steam Machines 的硬件周期，并且在期间只推出 1-2 台机型也有助于帮助解决问题，更不用说他们还可以依次和开发商们确立性能的基准线。我不知道 OEM 合作商们该怎么办；如果 Valve 专注于自己的几台设备里，OEM 们很可能会变得多余甚至拖平台后腿。

我觉得修复软件问题是最关键的。很多问题在严重拖着 Steam Machines 的后腿，比如缺少主机上遍地都是、又能轻易安装在 PC 上的的 Netflix 和 Twitch，那么即使做好了客厅体验问题依然是严重的败笔。即使 Valve 已经在逐步购买电影的版权以便在 Steam 上发售，我觉得用户还是会倾向于去使用已经在市场上建立口碑的一些流媒体服务。这些问题需要被严肃地对待，因为玩家日益倾向于把主机作为家庭影院系统的一部分。同时，修复 Steam 客户端和平台的问题也很重要，和更多第三方服务商合作增加内容应该会是个好主意。性能问题和 Linux 下的显卡问题也很严重，不过好在他们最近在慢慢进步。移植游戏也是个问题。类似 Feral Interactive 或者 Aspyr Media 这样的游戏移植商可以帮助扩展 Steam 的商店游戏数量，但联系开发者和出版社可能会有问题，而且这两家移植商经常在移植的容器上搞自己的花样。Valve 已经在帮助游戏工作室自己移植游戏了，比如 Rocket League，不过这种情况很少见，而且就算 Valve 去帮忙了，也是非常符合 Valve 拖拉的风格。而 AAA 大作这一块内容也绝不应该被忽略 —— 近来这方面的情况已经有极大好转了，虽然 Linux 平台的支持好了很多，但在玩家数量不够以及 Valve 为 Steam Machines 提供的开发帮助甚少的情况下，Bethesda 这样的开发商依然不愿意移植游戏；同时，也有像 Denuvo 一样缺乏数字版权管理的公司难以向 Steam Machines 移植游戏。

在我看来 Valve 需要在除了软件和硬件的地方也多花些功夫。如果他们只有一个机型的话，他们可以很方便的在硬件生产上贴点钱。这样 Steam Machines 的价格就能跻身主机的行列，而且还能比自己组装 PC 要便宜。针对正确的市场群体做营销也很关键，即便我们还不知道目标玩家应该是谁（我个人会对这样的 Steam Machines 感兴趣，而且我有一整堆已经在 Steam 上以相当便宜的价格买下的游戏）。最后，我觉得零售商们其实不会对 Valve 的计划很感冒，毕竟他们要靠卖和倒卖实体游戏赚钱。

就算 Valve 在产品和平台上采纳过这些改进，我也不知道怎样才能激活 Steam Machines 的全市场潜力。总的来说，Valve 不仅得学习自己的经验教训，还应该参考曾经有过类似尝试的厂商们，比如尝试依靠开放平台的 3DO 和 Pippin；又或者那些从台式机体验的竞争力退赛的那些公司，其实 Valve 如今的情况和他们也有几分相似。亦或者他们也可以观察一下任天堂 Switch —— 毕竟任天堂也在尝试跨界的创新。

*注解: 上述点子由 liamdawe 整理，所有的想法都由用户提交。*

本文是被一位访客提交的，我们欢迎大家前来[提交文章](https://www.gamingonlinux.com/submit-article/)。

---

via: <https://www.gamingonlinux.com/articles/user-editorial-steam-machines-steamos-after-a-year-in-the-wild.8474>

作者：[calvin](https://www.gamingonlinux.com/profiles/5163) 译者：[Moelf](https://github.com/Moelf) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


JavaScript 函数式编程介绍

Summary: 探索函数式编程，通过它让你的程序更具有可读性和易于调试

探索函数式编程，通过它让你的程序更具有可读性和易于调试

>
> 探索函数式编程，通过它让你的程序更具有可读性和易于调试
>
>
>

![](/data/attachment/album/201710/23/233346q2rwp5pozbopfgij.png)

当 Brendan Eich 在 1995 年创造 JavaScript 时，他原本打算[将 Scheme 移植到浏览器里](https://brendaneich.com/2008/04/popularity/) 。Scheme 作为 Lisp 的方言，是一种函数式编程语言。而当 Eich 被告知新的语言应该是一种可以与 Java 相比的脚本语言后，他最终确立了一种拥有 C 风格语法的语言（也和 Java 一样），但将函数视作一等公民。而 Java 直到版本 8 才从技术上将函数视为一等公民，虽然你可以用匿名类来模拟它。这个特性允许 JavaScript 通过函数式范式编程。

JavaScript 是一个多范式语言，允许你自由地混合和使用面向对象式、过程式和函数式的编程范式。最近，函数式编程越来越火热。在诸如 [Angular](https://angular-2-training-book.rangle.io/handout/change-detection/change_detection_strategy_onpush.html) 和 [React](https://facebook.github.io/react/docs/optimizing-performance.html#the-power-of-not-mutating-data) 这样的框架中，通过使用不可变数据结构可以切实提高性能。不可变是函数式编程的核心原则，它以及纯函数使得编写和调试程序变得更加容易。使用函数来代替程序的循环可以提高程序的可读性并使它更加优雅。总之，函数式编程拥有很多优点。

### 什么不是函数式编程

在讨论什么是函数式编程前，让我们先排除那些不属于函数式编程的东西。实际上它们是你需要丢弃的语言组件（再见，老朋友）：

* 循环：
	+ `while`
	+ `do...while`
	+ `for`
	+ `for...of`
	+ `for...in`
* 用 `var` 或者 `let` 来声明变量
* 没有返回值的函数
* 改变对象的属性 (比如: `o.x = 5;`)
* 改变数组本身的方法：
	+ `copyWithin`
	+ `fill`
	+ `pop`
	+ `push`
	+ `reverse`
	+ `shift`
	+ `sort`
	+ `splice`
	+ `unshift`
* 改变映射本身的方法：
	+ `clear`
	+ `delete`
	+ `set`
* 改变集合本身的方法：
	+ `add`
	+ `clear`
	+ `delete`

脱离这些特性应该如何编写程序呢？这是我们将在后面探索的问题。

### 纯函数

你的程序中包含函数不一定意味着你正在进行函数式编程。函数式范式将<ruby> 纯函数 <rt>  pure function </rt></ruby>和<ruby> 非纯函数 <rt>  impure function </rt></ruby>区分开。鼓励你编写纯函数。纯函数必须满足下面的两个属性：

* 引用透明：函数在传入相同的参数后永远返回相同的返回值。这意味着该函数不依赖于任何可变状态。
* 无副作用：函数不能导致任何副作用。副作用可能包括 I/O（比如向终端或者日志文件写入），改变一个不可变的对象，对变量重新赋值等等。

我们来看一些例子。首先，`multiply` 就是一个纯函数的例子，它在传入相同的参数后永远返回相同的返回值，并且不会导致副作用。

```
function multiply(a, b) {
  return a * b;
}

```

下面是非纯函数的例子。`canRide` 函数依赖捕获的 `heightRequirement` 变量。被捕获的变量不一定导致一个函数是非纯函数，除非它是一个可变的变量（或者可以被重新赋值）。这种情况下使用 `let` 来声明这个变量，意味着可以对它重新赋值。`multiply` 函数是非纯函数，因为它会导致在 console 上输出。

```
let heightRequirement = 46;

// Impure because it relies on a mutable (reassignable) variable.
function canRide(height) {
  return height >= heightRequirement;
}

// Impure because it causes a side-effect by logging to the console.
function multiply(a, b) {
  console.log('Arguments: ', a, b);
  return a * b;
}

```

下面的列表包含着 JavaScript 内置的非纯函数。你可以指出它们不满足两个属性中的哪个吗？

* `console.log`
* `element.addEventListener`
* `Math.random`
* `Date.now`
* `$.ajax` (这里 `$` 代表你使用的 Ajax 库)

理想的程序中所有的函数都是纯函数，但是从上面的函数列表可以看出，任何有意义的程序都将包含非纯函数。大多时候我们需要进行 AJAX 调用，检查当前日期或者获取一个随机数。一个好的经验法则是遵循 80/20 规则：函数中有 80％ 应该是纯函数，剩下的 20％ 的必要性将不可避免地是非纯函数。

使用纯函数有几个优点：

* 它们很容易导出和调试，因为它们不依赖于可变的状态。
* 返回值可以被缓存或者“记忆”来避免以后重复计算。
* 它们很容易测试，因为没有需要模拟（mock）的依赖（比如日志，AJAX，数据库等等）。

你编写或者使用的函数返回空（换句话说它没有返回值），那代表它是非纯函数。

### 不变性

让我们回到捕获变量的概念上。来看看 `canRide` 函数。我们认为它是一个非纯函数，因为 `heightRequirement` 变量可以被重新赋值。下面是一个构造出来的例子来说明如何用不可预测的值来对它重新赋值。

```
let heightRequirement = 46;

function canRide(height) {
  return height >= heightRequirement;
}

// Every half second, set heightRequirement to a random number between 0 and 200.
setInterval(() => heightRequirement = Math.floor(Math.random() * 201), 500);

const mySonsHeight = 47;

// Every half second, check if my son can ride.
// Sometimes it will be true and sometimes it will be false.
setInterval(() => console.log(canRide(mySonsHeight)), 500);

```

我要再次强调被捕获的变量不一定会使函数成为非纯函数。我们可以通过只是简单地改变 `heightRequirement` 的声明方式来使 `canRide` 函数成为纯函数。

```
const heightRequirement = 46;

function canRide(height) {
  return height >= heightRequirement;
}

```

通过用 `const` 来声明变量意味着它不能被再次赋值。如果尝试对它重新赋值，运行时引擎将抛出错误；那么，如果用对象来代替数字来存储所有的“常量”怎么样？

```
const constants = {
  heightRequirement: 46,
  // ... other constants go here
};

function canRide(height) {
  return height >= constants.heightRequirement;
}

```

我们用了 `const` ，所以这个变量不能被重新赋值，但是还有一个问题：这个对象可以被改变。下面的代码展示了，为了真正使其不可变，你不仅需要防止它被重新赋值，你也需要不可变的数据结构。JavaScript 语言提供了 `Object.freeze` 方法来阻止对象被改变。

```
'use strict';

// CASE 1: 对象的属性是可变的，并且变量可以被再次赋值。
let o1 = { foo: 'bar' };

// 改变对象的属性
o1.foo = 'something different';

// 对变量再次赋值
o1 = { message: "I'm a completely new object" };

// CASE 2: 对象的属性还是可变的，但是变量不能被再次赋值。
const o2 = { foo: 'baz' };

// 仍然能改变对象
o2.foo = 'Something different, yet again';

// 不能对变量再次赋值
// o2 = { message: 'I will cause an error if you uncomment me' }; // Error!

// CASE 3: 对象的属性是不可变的，但是变量可以被再次赋值。
let o3 = Object.freeze({ foo: "Can't mutate me" });

// 不能改变对象的属性
// o3.foo = 'Come on, uncomment me. I dare ya!'; // Error!

// 还是可以对变量再次赋值
o3 = { message: "I'm some other object, and I'm even mutable -- so take that!" };

// CASE 4: 对象的属性是不可变的，并且变量不能被再次赋值。这是我们想要的！！！！！！！！
const o4 = Object.freeze({ foo: 'never going to change me' });

// 不能改变对象的属性
// o4.foo = 'talk to the hand' // Error!

// 不能对变量再次赋值
// o4 = { message: "ain't gonna happen, sorry" }; // Error

```

不变性适用于所有的数据结构，包括数组、映射和集合。它意味着不能调用例如 `Array.prototype.push` 等会导致本身改变的方法，因为它会改变已经存在的数组。可以通过创建一个含有原来元素和新加元素的新数组，而不是将新元素加入一个已经存在的数组。其实所有会导致数组本身被修改的方法都可以通过一个返回修改好的新数组的函数代替。

```
'use strict';

const a = Object.freeze([4, 5, 6]);

// Instead of: a.push(7, 8, 9);
const b = a.concat(7, 8, 9);

// Instead of: a.pop();
const c = a.slice(0, -1);

// Instead of: a.unshift(1, 2, 3);
const d = [1, 2, 3].concat(a);

// Instead of: a.shift();
const e = a.slice(1);

// Instead of: a.sort(myCompareFunction);
const f = R.sort(myCompareFunction, a); // R = Ramda

// Instead of: a.reverse();
const g = R.reverse(a); // R = Ramda

// 留给读者的练习:
// copyWithin
// fill
// splice

```

[映射](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) 和 [集合](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) 也很相似。可以通过返回一个新的修改好的映射或者集合来代替使用会修改其本身的函数。

```
const map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three']
]);

// Instead of: map.set(4, 'four');
const map2 = new Map([...map, [4, 'four']]);

// Instead of: map.delete(1);
const map3 = new Map([...map].filter(([key]) => key !== 1));

// Instead of: map.clear();
const map4 = new Map();

```

```
const set = new Set(['A', 'B', 'C']);

// Instead of: set.add('D');
const set2 = new Set([...set, 'D']);

// Instead of: set.delete('B');
const set3 = new Set([...set].filter(key => key !== 'B'));

// Instead of: set.clear();
const set4 = new Set();

```

我想提一句如果你在使用 TypeScript（我非常喜欢 TypeScript），你可以用 `Readonly<T>`、`ReadonlyArray<T>`、`ReadonlyMap<K, V>` 和 `ReadonlySet<T>` 接口来在编译期检查你是否尝试更改这些对象，有则抛出编译错误。如果在对一个对象字面量或者数组调用 `Object.freeze`，编译器会自动推断它是只读的。由于映射和集合在其内部表达，所以在这些数据结构上调用 `Object.freeze` 不起作用。但是你可以轻松地告诉编译器它们是只读的变量。

![TypeScript Readonly Interfaces](/data/attachment/album/201710/23/233446z6jofno6l5elfxtj.png "TypeScript Readonly Interfaces")

*TypeScript 只读接口*

好，所以我们可以通过创建新的对象来代替修改原来的对象，但是这样不会导致性能损失吗？当然会。确保在你自己的应用中做了性能测试。如果你需要提高性能，可以考虑使用 [Immutable.js](https://facebook.github.io/immutable-js/)。Immutable.js 用[持久的数据结构](https://en.wikipedia.org/wiki/Persistent_data_structure) 实现了[链表](https://facebook.github.io/immutable-js/docs/#/List)、[堆栈](https://facebook.github.io/immutable-js/docs/#/Stack)、[映射](https://facebook.github.io/immutable-js/docs/#/Map)、[集合](https://facebook.github.io/immutable-js/docs/#/Set)和其他数据结构。使用了如同 Clojure 和 Scala 这样的函数式语言中相同的技术。

```
// Use in place of `[]`.
const list1 = Immutable.List(['A', 'B', 'C']);
const list2 = list1.push('D', 'E');

console.log([...list1]); // ['A', 'B', 'C']
console.log([...list2]); // ['A', 'B', 'C', 'D', 'E']

// Use in place of `new Map()`
const map1 = Immutable.Map([
  ['one', 1],
  ['two', 2],
  ['three', 3]
]);
const map2 = map1.set('four', 4);

console.log([...map1]); // [['one', 1], ['two', 2], ['three', 3]]
console.log([...map2]); // [['one', 1], ['two', 2], ['three', 3], ['four', 4]]

// Use in place of `new Set()`
const set1 = Immutable.Set([1, 2, 3, 3, 3, 3, 3, 4]);
const set2 = set1.add(5);

console.log([...set1]); // [1, 2, 3, 4]
console.log([...set2]); // [1, 2, 3, 4, 5]
```

### 函数组合

记不记得在中学时我们学过一些像 `(f ∘ g)(x)` 的东西？你那时可能想，“我什么时候会用到这些？”，好了，现在就用到了。你准备好了吗？`f ∘ g`读作 “函数 f 和函数 g 组合”。对它的理解有两种等价的方式，如等式所示： `(f ∘ g)(x) = f(g(x))`。你可以认为 `f ∘ g` 是一个单独的函数，或者视作将调用函数 `g` 的结果作为参数传给函数 `f`。注意这些函数是从右向左依次调用的，先执行 `g`，接下来执行 `f`。

关于函数组合的几个要点:

1. 我们可以组合任意数量的函数（不仅限于 2 个）。
2. 组合函数的一个方式是简单地把一个函数的输出作为下一个函数的输入（比如 `f(g(x))`）。

```
// h(x) = x + 1
// number -> number
function h(x) {
  return x + 1;
}

// g(x) = x^2
// number -> number
function g(x) {
  return x * x;
}

// f(x) = convert x to string
// number -> string
function f(x) {
  return x.toString();
}

// y = (f ∘ g ∘ h)(1)
const y = f(g(h(1)));
console.log(y); // '4'

```

[Ramda](http://ramdajs.com/) 和 [lodash](https://github.com/lodash/lodash/wiki/FP-Guide) 之类的库提供了更优雅的方式来组合函数。我们可以在更多的在数学意义上处理函数组合，而不是简单地将一个函数的返回值传递给下一个函数。我们可以创建一个由这些函数组成的单一复合函数(就是 `(f ∘ g)(x)`)。

```
// h(x) = x + 1
// number -> number
function h(x) {
  return x + 1;
}

// g(x) = x^2
// number -> number
function g(x) {
  return x * x;
}

// f(x) = convert x to string
// number -> string
function f(x) {
  return x.toString();
}

// R = Ramda
// composite = (f ∘ g ∘ h)
const composite = R.compose(f, g, h);

// Execute single function to get the result.
const y = composite(1);
console.log(y); // '4'

```

好了，我们可以在 JavaScript 中组合函数了。接下来呢？好，如果你已经入门了函数式编程，理想中你的程序将只有函数的组合。代码里没有循环（`for`, `for...of`, `for...in`, `while`, `do`），基本没有。你可能觉得那是不可能的。并不是这样。我们下面的两个话题是：递归和高阶函数。

### 递归

假设你想实现一个计算数字的阶乘的函数。 让我们回顾一下数学中阶乘的定义：

`n! = n * (n-1) * (n-2) * ... * 1`.

`n!` 是从 `n` 到 `1` 的所有整数的乘积。我们可以编写一个循环轻松地计算出结果。

```
function iterativeFactorial(n) {
  let product = 1;
  for (let i = 1; i <= n; i++) {
    product *= i;
  }
  return product;
}

```

注意 `product` 和 `i` 都在循环中被反复重新赋值。这是解决这个问题的标准过程式方法。如何用函数式的方法解决这个问题呢？我们需要消除循环，确保没有变量被重新赋值。递归是函数式程序员的最有力的工具之一。递归需要我们将整体问题分解为类似整体问题的子问题。

计算阶乘是一个很好的例子，为了计算 `n!` 我们需要将 n 乘以所有比它小的正整数。它的意思就相当于：

`n! = n * (n-1)!`

啊哈！我们发现了一个解决 `(n-1)!` 的子问题，它类似于整个问题 `n!`。还有一个需要注意的地方就是基础条件。基础条件告诉我们何时停止递归。 如果我们没有基础条件，那么递归将永远持续。 实际上，如果有太多的递归调用，程序会抛出一个堆栈溢出错误。啊哈！

```
function recursiveFactorial(n) {
  // Base case -- stop the recursion
  if (n === 0) {
    return 1; // 0! is defined to be 1.
  }
  return n * recursiveFactorial(n - 1);
}

```

然后我们来计算 `recursiveFactorial(20000)` 因为……，为什么不呢？当我们这样做的时候，我们得到了这个结果：

![Stack overflow error](/data/attachment/album/201710/23/233447bg5wglljjz99gf2s.png "Stack overflow error")

*堆栈溢出错误*

这里发生了什么？我们得到一个堆栈溢出错误！这不是无穷的递归导致的。我们已经处理了基础条件(`n === 0` 的情况)。那是因为浏览器的堆栈大小是有限的，而我们的代码使用了越过了这个大小的堆栈。每次对 `recursiveFactorial` 的调用导致了新的帧被压入堆栈中，就像一个盒子压在另一个盒子上。每当 `recursiveFactorial` 被调用，一个新的盒子被放在最上面。下图展示了在计算 `recursiveFactorial(3)` 时堆栈的样子。注意在真实的堆栈中，堆栈顶部的帧将存储在执行完成后应该返回的内存地址，但是我选择用变量 `r` 来表示返回值，因为 JavaScript 开发者一般不需要考虑内存地址。

![The stack for recursively calculating 3! (three factorial)](/data/attachment/album/201710/23/233447plcywxldy9qldwq0.png "The stack for recursively calculating 3! (three factorial)")

*递归计算 3! 的堆栈（三次乘法）*

你可能会想象当计算 `n = 20000` 时堆栈会更高。我们可以做些什么优化它吗？当然可以。作为 ES2015 (又名 ES6) 标准的一部分，有一个优化用来解决这个问题。它被称作<ruby> 尾调用优化 <rt>  proper tail calls optimization </rt></ruby>（PTC）。当递归函数做的最后一件事是调用自己并返回结果的时候，它使得浏览器删除或者忽略堆栈帧。实际上，这个优化对于相互递归函数也是有效的，但是为了简单起见，我们还是来看单一递归函数。

你可能会注意到，在递归函数调用之后，还要进行一次额外的计算（`n * r`）。那意味着浏览器不能通过 PTC 来优化递归；然而，我们可以通过重写函数使最后一步变成递归调用以便优化。一个窍门是将中间结果（在这里是 `product`）作为参数传递给函数。

```
'use strict';

// Optimized for tail call optimization.
function factorial(n, product = 1) {
  if (n === 0) {
    return product;
  }
  return factorial(n - 1, product * n)
}

```

让我们来看看优化后的计算 `factorial(3)` 时的堆栈。如下图所示，堆栈不会增长到超过两层。原因是我们把必要的信息都传到了递归函数中（比如 `product`）。所以，在 `product` 被更新后，浏览器可以丢弃掉堆栈中原先的帧。你可以在图中看到每次最上面的帧下沉变成了底部的帧，原先底部的帧被丢弃，因为不再需要它了。

![The optimized stack for recursively calculating 3! (three factorial) using PTC](/data/attachment/album/201710/23/233448xpfd9wdfkqaqv9ip.png "The optimized stack for recursively calculating 3! (three factorial) using PTC")

*递归计算 3! 的堆栈（三次乘法）使用 PTC*

现在选一个浏览器运行吧，假设你在使用 Safari，你会得到 `Infinity`（它是比在 JavaScript 中能表达的最大值更大的数）。但是我们没有得到堆栈溢出错误，那很不错！现在在其他的浏览器中呢怎么样呢？Safari 可能现在乃至将来是实现 PTC 的唯一一个浏览器。看看下面的兼容性表格：

![PTC compatibility](/data/attachment/album/201710/23/233449yle0huli5njzl1dl.png "PTC compatibility")

*PTC 兼容性*

其他浏览器提出了一种被称作<ruby> <a href="https://github.com/tc39/proposal-ptc-syntax#syntactic-tail-calls-stc">  语法级尾调用 </a> <rt>  syntactic tail calls </rt></ruby>（STC）的竞争标准。“语法级”意味着你需要用新的语法来标识你想要执行尾递归优化的函数。即使浏览器还没有广泛支持，但是把你的递归函数写成支持尾递归优化的样子还是一个好主意。

### 高阶函数

我们已经知道 JavaScript 将函数视作一等公民，可以把函数像其他值一样传递。所以，把一个函数传给另一个函数也很常见。我们也可以让函数返回一个函数。就是它！我们有高阶函数。你可能已经很熟悉几个在 `Array.prototype` 中的高阶函数。比如 `filter`、`map` 和 `reduce` 就在其中。对高阶函数的一种理解是：它是接受（一般会调用）一个回调函数参数的函数。让我们来看看一些内置的高阶函数的例子：

```
const vehicles = [
  { make: 'Honda', model: 'CR-V', type: 'suv', price: 24045 },
  { make: 'Honda', model: 'Accord', type: 'sedan', price: 22455 },
  { make: 'Mazda', model: 'Mazda 6', type: 'sedan', price: 24195 },
  { make: 'Mazda', model: 'CX-9', type: 'suv', price: 31520 },
  { make: 'Toyota', model: '4Runner', type: 'suv', price: 34210 },
  { make: 'Toyota', model: 'Sequoia', type: 'suv', price: 45560 },
  { make: 'Toyota', model: 'Tacoma', type: 'truck', price: 24320 },
  { make: 'Ford', model: 'F-150', type: 'truck', price: 27110 },
  { make: 'Ford', model: 'Fusion', type: 'sedan', price: 22120 },
  { make: 'Ford', model: 'Explorer', type: 'suv', price: 31660 }
];

const averageSUVPrice = vehicles
  .filter(v => v.type === 'suv')
  .map(v => v.price)
  .reduce((sum, price, i, array) => sum + price / array.length, 0);

console.log(averageSUVPrice); // 33399

```

注意我们在一个数组对象上调用其方法，这是面向对象编程的特性。如果我们想要更函数式一些，我们可以用 Rmmda 或者 lodash/fp 提供的函数。注意如果我们使用 `R.compose` 的话，需要倒转函数的顺序，因为它从右向左依次调用函数（从底向上）；然而，如果我们想从左向右调用函数就像上面的例子，我们可以用 `R.pipe`。下面两个例子用了 Rmmda。注意 Rmmda 有一个 `mean` 函数用来代替 `reduce` 。

```
const vehicles = [
  { make: 'Honda', model: 'CR-V', type: 'suv', price: 24045 },
  { make: 'Honda', model: 'Accord', type: 'sedan', price: 22455 },
  { make: 'Mazda', model: 'Mazda 6', type: 'sedan', price: 24195 },
  { make: 'Mazda', model: 'CX-9', type: 'suv', price: 31520 },
  { make: 'Toyota', model: '4Runner', type: 'suv', price: 34210 },
  { make: 'Toyota', model: 'Sequoia', type: 'suv', price: 45560 },
  { make: 'Toyota', model: 'Tacoma', type: 'truck', price: 24320 },
  { make: 'Ford', model: 'F-150', type: 'truck', price: 27110 },
  { make: 'Ford', model: 'Fusion', type: 'sedan', price: 22120 },
  { make: 'Ford', model: 'Explorer', type: 'suv', price: 31660 }
];

// Using `pipe` executes the functions from top-to-bottom.
const averageSUVPrice1 = R.pipe(
  R.filter(v => v.type === 'suv'),
  R.map(v => v.price),
  R.mean
)(vehicles);

console.log(averageSUVPrice1); // 33399

// Using `compose` executes the functions from bottom-to-top.
const averageSUVPrice2 = R.compose(
  R.mean,
  R.map(v => v.price),
  R.filter(v => v.type === 'suv')
)(vehicles);

console.log(averageSUVPrice2); // 33399

```

使用函数式方法的优点是清楚地分开了数据（`vehicles`）和逻辑（函数 `filter`，`map` 和 `reduce`）。面向对象的代码相比之下把数据和函数用以方法的对象的形式混合在了一起。

### 柯里化

不规范地说，<ruby> 柯里化 <rt>  currying </rt></ruby>是把一个接受 `n` 个参数的函数变成 `n` 个每个接受单个参数的函数的过程。函数的 `arity` 是它接受参数的个数。接受一个参数的函数是 `unary`，两个的是 `binary`，三个的是 `ternary`，`n` 个的是 `n-ary`。那么，我们可以把柯里化定义成将一个 `n-ary` 函数转换成 `n` 个 `unary` 函数的过程。让我们通过简单的例子开始，一个计算两个向量点积的函数。回忆一下线性代数，两个向量 `[a, b, c]` 和 `[x, y, z]` 的点积是 `ax + by + cz`。

```
function dot(vector1, vector2) {
  return vector1.reduce((sum, element, index) => sum += element * vector2[index], 0);
}

const v1 = [1, 3, -5];
const v2 = [4, -2, -1];

console.log(dot(v1, v2)); // 1(4) + 3(-2) + (-5)(-1) = 4 - 6 + 5 = 3

```

`dot` 函数是 binary，因为它接受两个参数；然而我们可以将它手动转换成两个 unary 函数，就像下面的例子。注意 `curriedDot` 是一个 unary 函数，它接受一个向量并返回另一个接受第二个向量的 unary 函数。

```
function curriedDot(vector1) {
  return function(vector2) {
    return vector1.reduce((sum, element, index) => sum += element * vector2[index], 0);
  }
}

// Taking the dot product of any vector with [1, 1, 1]
// is equivalent to summing up the elements of the other vector.
const sumElements = curriedDot([1, 1, 1]);

console.log(sumElements([1, 3, -5])); // -1
console.log(sumElements([4, -2, -1])); // 1

```

很幸运，我们不需要把每一个函数都手动转换成柯里化以后的形式。[Ramda](http://ramdajs.com/docs/#curry) 和 [lodash](https://lodash.com/docs/4.17.4#curry) 等库可以为我们做这些工作。实际上，它们是柯里化的混合形式。你既可以每次传递一个参数，也可以像原来一样一次传递所有参数。

```
function dot(vector1, vector2) {
  return vector1.reduce((sum, element, index) => sum += element * vector2[index], 0);
}

const v1 = [1, 3, -5];
const v2 = [4, -2, -1];

// Use Ramda to do the currying for us!
const curriedDot = R.curry(dot);

const sumElements = curriedDot([1, 1, 1]);

console.log(sumElements(v1)); // -1
console.log(sumElements(v2)); // 1

// This works! You can still call the curried function with two arguments.
console.log(curriedDot(v1, v2)); // 3

```

Ramda 和 lodash 都允许你“跳过”一些变量之后再指定它们。它们使用置位符来做这些工作。因为点积的计算可以交换两项。传入向量的顺序不影响结果。让我们换一个例子来阐述如何使用一个置位符。Ramda 使用双下划线作为其置位符。

```
const giveMe3 = R.curry(function(item1, item2, item3) {
  return `
    1: ${item1}
    2: ${item2}
    3: ${item3}
  `;
});

const giveMe2 = giveMe3(R.__, R.__, 'French Hens');   // Specify the third argument.
const giveMe1 = giveMe2('Partridge in a Pear Tree');  // This will go in the first slot.
const result = giveMe1('Turtle Doves');               // Finally fill in the second argument.

console.log(result);
// 1: Partridge in a Pear Tree
// 2: Turtle Doves
// 3: French Hens

```

在我们结束探讨柯里化之前最后的议题是<ruby> 偏函数应用 <rt>  partial application </rt></ruby>。偏函数应用和柯里化经常同时出场，尽管它们实际上是不同的概念。一个柯里化的函数还是柯里化的函数，即使没有给它任何参数。偏函数应用，另一方面是仅仅给一个函数传递部分参数而不是所有参数。柯里化是偏函数应用常用的方法之一，但是不是唯一的。

JavaScript 拥有一个内置机制可以不依靠柯里化来做偏函数应用。那就是 [function.prototype.bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) 方法。这个方法的一个特殊之处在于，它要求你将 `this` 作为第一个参数传入。 如果你不进行面向对象编程，那么你可以通过传入 `null` 来忽略 `this`。

```
1function giveMe3(item1, item2, item3) {
  return `
    1: ${item1}
    2: ${item2}
    3: ${item3}
  `;
}

const giveMe2 = giveMe3.bind(null, 'rock');
const giveMe1 = giveMe2.bind(null, 'paper');
const result = giveMe1('scissors');

console.log(result);
// 1: rock
// 2: paper
// 3: scissors

```

### 总结

我希望你享受探索 JavaScript 中函数式编程的过程。对一些人来说，它可能是一个全新的编程范式，但我希望你能尝试它。你会发现你的程序更易于阅读和调试。不变性还将允许你优化 Angular 和 React 的性能。

*这篇文章基于 Matt 在 OpenWest 的演讲 [JavaScript the Good-er Parts](https://www.openwest.org/schedule/#talk-5). [OpenWest](https://www.openwest.org/) ~~将~~在 6/12-15 ,2017 在 Salt Lake City, Utah 举行。*

---

作者简介：

Matt Banz - Matt 于 2008 年五月在犹他大学获得了数学学位毕业。一个月后他得到了一份 web 开发者的工作，他从那时起就爱上了它！在 2013 年，他在北卡罗莱纳州立大学获得了计算机科学硕士学位。他在 LDS 商学院和戴维斯学区社区教育计划教授 Web 课程。他现在是就职于 Motorola Solutions 公司的高级前端开发者。

---

via: <https://opensource.com/article/17/6/functional-javascript>

作者：[Matt Banz](https://opensource.com/users/battmanz) 译者：[trnhoe](https://github.com/trnhoe) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


并发服务器（一）：简介

![](/data/attachment/album/201710/24/115622i0oaajarc8aaryn6.jpg)

这是关于并发网络服务器编程的第一篇教程。我计划测试几个主流的、可以同时处理多个客户端请求的服务器并发模型，基于可扩展性和易实现性对这些模型进行评判。所有的服务器都会监听套接字连接，并且实现一些简单的协议用于与客户端进行通讯。

该系列的所有文章：

* [第一节 - 简介](http://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/)
* [第二节 - 线程](http://eli.thegreenplace.net/2017/concurrent-servers-part-2-threads/)
* [第三节 - 事件驱动](http://eli.thegreenplace.net/2017/concurrent-servers-part-3-event-driven/)

### 协议

该系列教程所用的协议都非常简单，但足以展示并发服务器设计的许多有趣层面。而且这个协议是 *有状态的* —— 服务器根据客户端发送的数据改变内部状态，然后根据内部状态产生相应的行为。并非所有的协议都是有状态的 —— 实际上，基于 HTTP 的许多协议是无状态的，但是有状态的协议也是很常见，值得认真讨论。

在服务器端看来，这个协议的视图是这样的：

![](/data/attachment/album/201710/24/115654eddgnzthg4atdhh7.png)

总之：服务器等待新客户端的连接；当一个客户端连接的时候，服务器会向该客户端发送一个 `*` 字符，进入“等待消息”的状态。在该状态下，服务器会忽略客户端发送的所有字符，除非它看到了一个 `^` 字符，这表示一个新消息的开始。这个时候服务器就会转变为“正在通信”的状态，这时它会向客户端回送数据，把收到的所有字符的每个字节加 1 回送给客户端 注1 。当客户端发送了 `$` 字符，服务器就会退回到等待新消息的状态。`^` 和 `$` 字符仅仅用于分隔消息 —— 它们不会被服务器回送。

每个状态之后都有个隐藏的箭头指向 “等待客户端” 状态，用于客户端断开连接。因此，客户端要表示“我已经结束”的方法很简单，关掉它那一端的连接就好。

显然，这个协议是真实协议的简化版，真实使用的协议一般包含复杂的报文头、转义字符序列（例如让消息体中可以出现 `$` 符号），额外的状态变化。但是我们这个协议足以完成期望。

另一点：这个系列是介绍性的，并假设客户端都工作的很好（虽然可能运行很慢）；因此没有设置超时，也没有设置特殊的规则来确保服务器不会因为客户端的恶意行为（或是故障）而出现阻塞，导致不能正常结束。

### 顺序服务器

这个系列中我们的第一个服务端程序是一个简单的“顺序”服务器，用 C 进行编写，除了标准的 POSIX 中用于套接字的内容以外没有使用其它库。服务器程序是顺序，因为它一次只能处理一个客户端的请求；当有客户端连接时，像之前所说的那样，服务器会进入到状态机中，并且不再监听套接字接受新的客户端连接，直到当前的客户端结束连接。显然这不是并发的，而且即便在很少的负载下也不能服务多个客户端，但它对于我们的讨论很有用，因为我们需要的是一个易于理解的基础。

这个服务器的完整代码在[这里](https://github.com/eliben/code-for-blog/blob/master/2017/async-socket-server/sequential-server.c)；接下来，我会着重于一些重点的部分。`main` 函数里面的外层循环用于监听套接字，以便接受新客户端的连接。一旦有客户端进行连接，就会调用 `serve_connection`，这个函数中的代码会一直运行，直到客户端断开连接。

顺序服务器在循环里调用 `accept` 用来监听套接字，并接受新连接：

```
while (1) {
  struct sockaddr_in peer_addr;
  socklen_t peer_addr_len = sizeof(peer_addr);

  int newsockfd =
      accept(sockfd, (struct sockaddr*)&peer_addr, &peer_addr_len);

  if (newsockfd < 0) {
    perror_die("ERROR on accept");
  }

  report_peer_connected(&peer_addr, peer_addr_len);
  serve_connection(newsockfd);
  printf("peer done\n");
}

```

`accept` 函数每次都会返回一个新的已连接的套接字，然后服务器调用 `serve_connection`；注意这是一个 *阻塞式* 的调用 —— 在 `serve_connection` 返回前，`accept` 函数都不会再被调用了；服务器会被阻塞，直到客户端结束连接才能接受新的连接。换句话说，客户端按 *顺序* 得到响应。

这是 `serve_connection` 函数：

```
typedef enum { WAIT_FOR_MSG, IN_MSG } ProcessingState;

void serve_connection(int sockfd) {
  if (send(sockfd, "*", 1, 0) < 1) {
    perror_die("send");
  }

  ProcessingState state = WAIT_FOR_MSG;

  while (1) {
    uint8_t buf[1024];
    int len = recv(sockfd, buf, sizeof buf, 0);
    if (len < 0) {
      perror_die("recv");
    } else if (len == 0) {
      break;
    }

    for (int i = 0; i < len; ++i) {
      switch (state) {
      case WAIT_FOR_MSG:
        if (buf[i] == '^') {
          state = IN_MSG;
        }
        break;
      case IN_MSG:
        if (buf[i] == '$') {
          state = WAIT_FOR_MSG;
        } else {
          buf[i] += 1;
          if (send(sockfd, &buf[i], 1, 0) < 1) {
            perror("send error");
            close(sockfd);
            return;
          }
        }
        break;
      }
    }
  }

  close(sockfd);
}

```

它完全是按照状态机协议进行编写的。每次循环的时候，服务器尝试接收客户端的数据。收到 0 字节意味着客户端断开连接，然后循环就会退出。否则，会逐字节检查接收缓存，每一个字节都可能会触发一个状态。

`recv` 函数返回接收到的字节数与客户端发送消息的数量完全无关（`^...$` 闭合序列的字节）。因此，在保持状态的循环中遍历整个缓冲区很重要。而且，每一个接收到的缓冲中可能包含多条信息，但也有可能开始了一个新消息，却没有显式的结束字符；而这个结束字符可能在下一个缓冲中才能收到，这就是处理状态在循环迭代中进行维护的原因。

例如，试想主循环中的 `recv` 函数在某次连接中返回了三个非空的缓冲：

1. `^abc$de^abte$f`
2. `xyz^123`
3. `25$^ab$abab`

服务端返回的是哪些数据？追踪代码对于理解状态转变很有用。（答案见 注   2  ）

### 多个并发客户端

如果多个客户端在同一时刻向顺序服务器发起连接会发生什么事情？

服务器端的代码（以及它的名字 “顺序服务器”）已经说的很清楚了，一次只能处理 *一个* 客户端的请求。只要服务器在 `serve_connection` 函数中忙于处理客户端的请求，就不会接受别的客户端的连接。只有当前的客户端断开了连接，`serve_connection` 才会返回，然后最外层的循环才能继续执行接受其他客户端的连接。

为了演示这个行为，[该系列教程的示例代码](https://github.com/eliben/code-for-blog/tree/master/2017/async-socket-server) 包含了一个 Python 脚本，用于模拟几个想要同时连接服务器的客户端。每一个客户端发送类似之前那样的三个数据缓冲  注3 ，不过每次发送数据之间会有一定延迟。

客户端脚本在不同的线程中并发地模拟客户端行为。这是我们的序列化服务器与客户端交互的信息记录：

```
$ python3.6 simple-client.py  -n 3 localhost 9090
INFO:2017-09-16 14:14:17,763:conn1 connected...
INFO:2017-09-16 14:14:17,763:conn1 sending b'^abc$de^abte$f'
INFO:2017-09-16 14:14:17,763:conn1 received b'b'
INFO:2017-09-16 14:14:17,802:conn1 received b'cdbcuf'
INFO:2017-09-16 14:14:18,764:conn1 sending b'xyz^123'
INFO:2017-09-16 14:14:18,764:conn1 received b'234'
INFO:2017-09-16 14:14:19,764:conn1 sending b'25$^ab0000$abab'
INFO:2017-09-16 14:14:19,765:conn1 received b'36bc1111'
INFO:2017-09-16 14:14:19,965:conn1 disconnecting
INFO:2017-09-16 14:14:19,966:conn2 connected...
INFO:2017-09-16 14:14:19,967:conn2 sending b'^abc$de^abte$f'
INFO:2017-09-16 14:14:19,967:conn2 received b'b'
INFO:2017-09-16 14:14:20,006:conn2 received b'cdbcuf'
INFO:2017-09-16 14:14:20,968:conn2 sending b'xyz^123'
INFO:2017-09-16 14:14:20,969:conn2 received b'234'
INFO:2017-09-16 14:14:21,970:conn2 sending b'25$^ab0000$abab'
INFO:2017-09-16 14:14:21,970:conn2 received b'36bc1111'
INFO:2017-09-16 14:14:22,171:conn2 disconnecting
INFO:2017-09-16 14:14:22,171:conn0 connected...
INFO:2017-09-16 14:14:22,172:conn0 sending b'^abc$de^abte$f'
INFO:2017-09-16 14:14:22,172:conn0 received b'b'
INFO:2017-09-16 14:14:22,210:conn0 received b'cdbcuf'
INFO:2017-09-16 14:14:23,173:conn0 sending b'xyz^123'
INFO:2017-09-16 14:14:23,174:conn0 received b'234'
INFO:2017-09-16 14:14:24,175:conn0 sending b'25$^ab0000$abab'
INFO:2017-09-16 14:14:24,176:conn0 received b'36bc1111'
INFO:2017-09-16 14:14:24,376:conn0 disconnecting

```

这里要注意连接名：`conn1` 是第一个连接到服务器的，先跟服务器交互了一段时间。接下来的连接 `conn2` —— 在第一个断开连接后，连接到了服务器，然后第三个连接也是一样。就像日志显示的那样，每一个连接让服务器变得繁忙，持续了大约 2.2 秒的时间（这实际上是人为地在客户端代码中加入的延迟），在这段时间里别的客户端都不能连接。

显然，这不是一个可扩展的策略。这个例子中，客户端中加入了延迟，让服务器不能处理别的交互动作。一个智能服务器应该能处理一堆客户端的请求，而这个原始的服务器在结束连接之前一直繁忙（我们将会在之后的章节中看到如何实现智能的服务器）。尽管服务端有延迟，但这不会过度占用 CPU；例如，从数据库中查找信息（时间基本上是花在连接到数据库服务器上，或者是花在硬盘中的本地数据库）。

### 总结及期望

这个示例服务器达成了两个预期目标：

1. 首先是介绍了问题范畴和贯彻该系列文章的套接字编程基础。
2. 对于并发服务器编程的抛砖引玉 —— 就像之前的部分所说，顺序服务器还不能在非常轻微的负载下进行扩展，而且没有高效的利用资源。

在看下一篇文章前，确保你已经理解了这里所讲的服务器/客户端协议，还有顺序服务器的代码。我之前介绍过了这个简单的协议；例如 [串行通信分帧](http://eli.thegreenplace.net/2009/08/12/framing-in-serial-communications/) 和 [用协程来替代状态机](http://eli.thegreenplace.net/2009/08/29/co-routines-as-an-alternative-to-state-machines)。要学习套接字网络编程的基础，[Beej 的教程](http://beej.us/guide/bgnet/) 用来入门很不错，但是要深入理解我推荐你还是看本书。

如果有什么不清楚的，请在评论区下进行评论或者向我发送邮件。深入理解并发服务器！

---

* 注1：状态转变中的 In/Out 记号是指 [Mealy machine](https://en.wikipedia.org/wiki/Mealy_machine)。
* 注2：回应的是 `bcdbcuf23436bc`。
* 注3：这里在结尾处有一点小区别，加了字符串 `0000` —— 服务器回应这个序列，告诉客户端让其断开连接；这是一个简单的握手协议，确保客户端有足够的时间接收到服务器发送的所有回复。

---

via: <https://eli.thegreenplace.net/2017/concurrent-servers-part-1-introduction/>

作者：[Eli Bendersky](https://eli.thegreenplace.net/pages/about) 译者：[GitFuture](https://github.com/GitFuture) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


如何成规模地部署多云的无服务器程序和 Cloud Foundry API

>
> IBM 的 Ken Parmelee 说：“微服务和 API 是产品，我们需要以这种方式思考。”
>
>
>

领导 IBM 的 API 网关和 Big Blue 开源项目的的 Ken Parmelee 对以开源方式 “进攻” API 以及如何创建微服务和使其伸缩有一些思考。

Parmelee 说：“微服务和 API 是产品，我们需要以这种方式思考这些问题。当你开始这么做，人们依赖它作为它们业务的一部分。这是你在这个领域所做的关键方面。”

![](/data/attachment/album/201710/24/121629hlwc8l2fh66h2x56.png)

他在最近的[北欧 APIs 2017 平台峰会](https://nordicapis.com/events/the-2017-api-platform-summit/)登上讲台，并挑战了一些流行的观念。

“快速失败不是一个很好的概念。你想在第一场比赛中获得一些非常棒的东西。这并不意味着你需要花费大量的时间，而是应该让它变得非常棒，然后不断的发展和改进。如果一开始真的很糟糕，人们就不会想要用你。”

他谈及包括 [OpenWhisk](https://developer.ibm.com/openwhisk/) 在内的 IBM 现代无服务器架构，这是一个 IBM 和 Apache 之间的开源伙伴关系。 云优先的基于分布式事件的编程服务是这两年多来重点关注这个领域的成果；IBM 是该领域领先的贡献者，它是 IBM 云服务的基础。它提供基础设施即服务（IaaS）、自动缩放、为多种语言提供支持、用户只需支付实际使用费用即可。这次旅程充满了挑战，因为他们发现服务器操作需要安全、并且需要轻松 —— 匿名访问、缺少使用路径、固定的 URL 格式等。

任何人都可以在 30 秒内在 <https://console.bluemix.net/openwhisk/> 上尝试这些无服务器 API。“这听起来很有噱头，但这是很容易做到的。我们正在结合 [Cloud Foundry 中完成的工作](https://cloudfoundry.org/the-foundry/ibm-cloud/)，并在 OpenWhisk 下的 Bluemix 中发布了它们，以提供安全性和可扩展性。”

他说：“灵活性对于微服务也是非常重要的。 当你使用 API 在现实世界中工作时，你开始需要跨云进行扩展。”这意味着从你的内部云走向公共云，并且“对你要怎么做有一个实在的概念很重要”。

![](/data/attachment/album/201710/24/121629p22es5hs2yxp5228.png)

在思考“任何云概念”的时候，他警告说，不是“将其放入一个 Docker 容器，并到处运行。这很棒，但需要在这些环境中有效运行。Docker 和 Kubernetes 有提供了很多帮助，但是你想要你的操作方式付诸实施。” 提前考虑 API 的使用，无论是在内部运行还是扩展到公有云并可以公开调用 - 你需要有这样的“架构观”，他补充道。

Parmelee 说：“我们都希望我们所创造的有价值，并被广泛使用。” API 越成功，将其提升到更高水平的挑战就越大。

![](/data/attachment/album/201710/24/121629z2cloo20zt922llp.png)

*API 是微服务或“服务间”的组成部分。*

他说，API 的未来是原生云的 - 无论你从哪里开始。关键因素是可扩展性，简化后端管理，降低成本，避免厂商锁定。

你可以在下面或在 [YouTube](https://www.youtube.com/jA25Kmxr6fU) 观看他整整 23 分钟的演讲。

---

via: <http://superuser.openstack.org/articles/deploy-multi-cloud-serverless-cloud-foundry-apis-scale/>

作者：[Superuser](http://superuser.openstack.org/articles/author/superuser/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


为什么要切换到 Linux 系统？我该怎么做？

>
> 是时候做出改变了。
>
>
>

![Ubuntu](/data/attachment/album/201710/24/233252mrfrprplezuem1rg.jpg)

当你在选购电脑的时候，你可能会在 [Windows](https://www.popsci.com/windows-tweaks-improve-performance) 和 [macOS](https://www.popsci.com/macos-tweaks-improve-performance) 之间犹豫，但是可能基本不会想到 Linux。尽管如此，这个名气没那么大的操作系统仍然拥有庞大而忠诚的粉丝。因为它相对于它的竞争者，有很大的优势。

不管你是完全不了解 Linux，或是已经尝试过一两次，我们希望你考虑一下在你的下一台笔记本或台式机上运行 Linux，或者可以和现存系统做个双启动。请继续阅读下去，看是不是时候该切换了。

### 什么是 Linux?

如果你已经非常熟悉 Linux，可以跳过这个部分。对于不熟悉的其他人，Linux 是一个免费的开源操作系统，所有人都可以去探索它的代码。技术上来说，术语“Linux”说的只是内核，或者核心代码。不过，人们一般用这个名字统称整个操作系统，包括界面和集成的应用。

因为所有人都可以修改它，Linux 有非常自由的可定制性，这鼓舞了很多程序员制作并发布了自己的系统——常称为<ruby> 发行版 <rt>  distro </rt></ruby>。这些不同口味的系统，每一个都有自己特色的软件和界面。一些比较有名的发行版，模仿了熟悉的 Windows 或 macOS 操作系统，比如 [Ubuntu](https://www.ubuntu.com/)、 [Linux Mint](https://linuxmint.com/) 和 [Zorin OS](https://zorinos.com/)。当你准备选择一个发行版时，可以去它们官网看一下，试试看是不是适合自己。

为了制作和维护这些 Linux 发行版，大量的开发者无偿地贡献了自己的时间。有时候，利润主导的公司为了拓展自己的软件销售领域，也会主导开发带有独特特性的 Linux 版本。比如 Android（它虽然不能当作一个完整的 Linux 操作系统）就是以基于 Linux 内核的，这也是为什么它有很多[不同变种](https://lineageos.org/)的原因。另外，很多服务器和数据中心也运行了 Linux，所以很有可能这个操作系统托管着你正在看的网页。

### 有什么好处？

首先，Linux 是免费而且开源的，意味着你可以将它安装到你现有的电脑或笔记本上，或者你自己组装的机器，而不用支付任何费用。系统会自带一些常用软件，包括网页浏览器、媒体播放器、[图像编辑器](https://www.gimp.org/)和[办公软件](https://www.libreoffice.org/)，所以你也不用为了查看图片或处理文档再支付其他额外费用。而且，以后还可以免费升级。

Linux 比其他系统能更好的防御恶意软件，强大到你都不需要运行杀毒软件。开发者们在最早构建时就考虑了安全性，比如说，操作系统只运行可信的软件。而且，很少有恶意软件针对这个系统，对于黑客来说，这样做没有价值。Linux 也并不是完全没有任何漏洞，只不过对于一般只运行已验证软件的家庭用户来说，并不用太担心安全性。

这个操作系统对硬件资源的要求比起数据臃肿的 Windows 或 macOS 来说也更少。一些发行版不像它们名气更大的表兄弟，默认集成了更少的组件，开发者特别开发了一些，比如 [Puppy Linux](http://puppylinux.org/main/Overview%20and%20Getting%20Started.htm) 和 [Linux Lite](https://www.linuxliteos.com/)，让系统尽可能地轻量。这让 Linux 非常适合那些家里有很老的电脑的人。如果你的远古笔记本正在原装操作系统的重压下喘息，试试装一个 Linux，应该会快很多。如果不愿意的话，你也不用抛弃旧系统，我们会在后面的部分里解释怎么做。

尽管你可能会需要一点时间来适应新系统，不过不用太久，你就会发现 Linux 界面很容易使用。任何年龄和任何技术水平的人都可以掌握这个软件。而且在线的 Linux 社区提供了大量的帮助和支持。说到社区，下载 Linux 也是对开源软件运动的支持：这些开发者一起工作，并不收取任何费用，为全球用户开发更优秀的软件。

### 我该从哪儿开始？

Linux 据说只有专家才能安装。不过比起前几年，现在安装并运行操作系统已经非常简单了。

首先，打开你喜欢的发行版的网站，按照上面的安装指南操作。一般会需要烧录一张 DVD 或者制作一个带有必要程序的 U 盘，然后重启你的电脑，执行这段程序。实际上，这个操作系统的一个好处是，你可以将它直接安装在可插拔的 U 盘上，我们有一个[如何把电脑装在 U 盘里](https://www.popsci.com/portable-computer-usb-stick)的完整指南。

如果你想在不影响原来旧系统的情况下运行 Linux，你可以选择从 DVD 或 U 盘或者电脑硬盘的某个分区（分成不同的区来独立运行不同的操作系统）单独启动。有些 Linux 发行版在安装过程中会帮你自动处理磁盘分区。或者你可以用[磁盘管理器](https://www.disk-partition.com/windows-10/windows-10-disk-management-0528.html) （Windows）或者[磁盘工具](https://support.apple.com/kb/PH22240?locale=en_US) （macOS）自己调整分区。

这些安装说明可能看上去很模糊，但是不要担心：每个发行版都会提供详细的安装指引，虽然大多数情况下过程都差不多。比如，如果你想安装 Ubuntu（最流行的家用 Linux 发行版中的一个），可以[参考这里的指引](https://tutorials.ubuntu.com/tutorial/tutorial-install-ubuntu-desktop?backURL=%2F#0)。（在安装之前，你也可以[尝试运行一下](https://tutorials.ubuntu.com/tutorial/try-ubuntu-before-you-install?backURL=%2F#0)）你需要下载最新的版本，烧录到 DVD 或是 U 盘里，然后再用光盘或 U 盘引导开机，然后跟随安装向导里的指引操作。安装完成时提示安装额外软件时，Ubuntu 会引导你打开合适的工具。

如果你要在一台全新的电脑上安装 Linux，那没什么需要特别留意的。不过如果你要保留旧系统的情况下安装新系统，我们建议你首先[备份自己的数据](https://www.popsci.com/back-up-and-protect-your-data)。在安装过程中，也要注意选择双启动选项，避免擦除现有的系统和文件。你选好的发行版的介绍里会有更详细的说明：你可以在[这里](https://zorinos.com/help/install-zorin-os/)查看 Zorin OS 的完整介绍，[这里](https://linuxmint.com/documentation.php)有 Linux Mint的，其他发行版的介绍在他们各自的网站上也都会有。

就这些了！那么，你准备好试试 Linux 了吗？

---

via: <https://www.popsci.com/switch-to-linux-operating-system>

作者：[David Nield](https://www.popsci.com/authors/david-nield) 译者：[zpl1025](https://github.com/zpl1025) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Ubuntu 18.04 LTS 定名为“仿生河狸”，将于明年 4 月 26 日发布

![](/data/attachment/album/201710/25/003510yue4yc1i4y7yzmfh.jpeg)

Ubuntu 母公司 Canonical 的 CEO， Mark Shuttleworth 刚刚披露，Ubuntu 操作系统的下一个长期支持版本（LTS）的代号定为 “Bionic Beaver”，意即“仿生河狸”，。它将于明年的 4 月发布。

按照 Ubuntu 系列的命名传统，Mark Shuttleworth 今天写的一篇[博文](http://www.markshuttleworth.com/archives/1518)对 Ubuntu 17.10（AA）“巧豚”的发布表示祝贺，并宣布了下一个版本的代号：

>
> “为纪念这一孜孜不倦的辛劳，这次我们的吉祥物是一个以其精力充沛的态度、勤劳的天性和工程能力而闻名的哺乳动物。同时，我们给它赋予 21 世纪的新精神，以纪念永不停息的 Ubuntu Core 机器人们。女士们、先生们，让我们欢迎 18.04 LTS —— 仿生河狸。”
>
>
>

### Ubuntu 18.04 LTS 将发布于 2018 年 4 月

虽然 Mark 并没有实际披露 Ubuntu 18.04 LTS 的计划，但是由于事实上他们将把更多的工作放在面向物联网设备的 Ubuntu Snappy Core 系统上，因此看起来其发布周期将会定在 2018 年 4 月 26 日。

Ubuntu 18.04 LTS 的开发工作会在两天后开始，当工具链上传到归档时， Ubuntu 18.04 LTS （BB）会和其前代一样，在六个月内发布两个 Alpha 和两个 Beta 版本之后正式发布。

据其[发布计划](https://wiki.ubuntu.com/BionicBeaver/ReleaseSchedule)，其第一个 Alpha 里程碑将在 2018 年 1 月 4 号发布，第二个在 2 月 1 日——但只有部分风味版本会发布 Alpha 版本，并且这些风味版会在 3 月 8 日发布第一个 Beta 版本，而发布前的最后一个 Beta 版本将于 2018 年 4 月 5 日发布。