精华内容
下载资源
问答
  • 计算与推断思维 、表格

    万次阅读 2017-11-18 21:05:17
    、表格 原文:Tables 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 表格是表示数据集的基本对象类型。 表格可以用两方式查看: 具名列的序列,每列都描述数据集中所有条目的一个方面,或者 ...

    五、表格

    原文:Tables

    译者:飞龙

    协议:CC BY-NC-SA 4.0

    自豪地采用谷歌翻译

    表格是表示数据集的基本对象类型。 表格可以用两种方式查看:

    • 具名列的序列,每列都描述数据集中所有条目的一个方面,或者
    • 行的序列,每行都包含数据集中单个条目的所有信息。

    为了使用表格,导入所有称为datascience的模块,这是为这篇文章创建的模块。

    from datascience import *

    空表格可以使用Table创建。空表格是实用的,因为他可以扩展来包含新行和新列。

    Table()

    表格上的with_columns方法使用带有附加标签的列,构造一个新表。 表格的每一列都是一个数组。 为了将一个新列添加到表中,请使用标签和数组调用with_columns。 (with_column方法具有相同的效果。)

    下面,我们用一个没有列的空表开始每个例子。

    Table().with_columns('Number of petals', make_array(8, 34, 5))
    Number of petals
    8
    34
    5

    为了添加两个(或更多)新列,请为每列提供一个数组和标签。 所有列必须具有相同的长度,否则会发生错误。

    Table().with_columns(
        'Number of petals', make_array(8, 34, 5),
        'Name', make_array('lotus', 'sunflower', 'rose')
    )
    Number of petalsName
    8lotus
    34sunflower
    5rose

    我们可以给这个表格一个名词,之后使用另外一列扩展表格。

    flowers = Table().with_columns(
        'Number of petals', make_array(8, 34, 5),
        'Name', make_array('lotus', 'sunflower', 'rose')
    )
    
    flowers.with_columns(
        'Color', make_array('pink', 'yellow', 'red')
    )
    Number of petalsNameColor
    8lotuspink
    34sunfloweryellow
    5rosered

    with_columns方法每次调用时,都会创建一个新表,所以原始表不受影响。 例如,表an_example仍然只有它创建时的两列。

    flowers
    Number of petalsName
    8lotus
    34sunflower
    5rose

    通过这种方式创建表涉及大量的输入。 如果数据已经输入到某个地方,通常可以使用 Python 将其读入表格中,而不是逐个单元格地输入。

    通常,表格从包含逗号分隔值的文件创建。这些文件被称为 CSV 文件。

    下面,我们使用Tableread_table方法,来读取一个 CSV 文件,它包含了一些数据,Minard 在他的拿破仑的俄罗斯战役的图片中使用。 数据放在名为minard的表中。

    minard = Table.read_table('minard.csv')
    minard
    LongitudeLatitudeCityDirectionSurvivors
    3254.8SmolenskAdvance145000
    33.254.9DorogobougeAdvance140000
    34.455.5ChjatAdvance127100
    37.655.8MoscouAdvance100000
    34.355.2WixmaRetreat55000
    3254.6SmolenskRetreat24000
    30.454.4OrschaRetreat20000
    26.854.3MoiodexnoRetreat12000

    我们将使用这个小的表格来演示一些有用的表格方法。 然后,我们将使用这些相同的方法,并在更大的数据表上开发其他方法。

    表格的大小

    num_columns方法提供了表中的列数量,num_rows是行数量。

    minard.num_columns
    5
    minard.num_rows
    8

    列标签

    labels方法可以用来列出所有列的标签。 对于minard,并不是特别有用,但是对于那些非常大的表格,并不是所有的列都在屏幕上可见。

    minard.labels
    ('Longitude', 'Latitude', 'City', 'Direction', 'Survivors')

    我们使用relabeled修改列标签。这会创建新的表格,并保留minard不变。

    minard.relabeled('City', 'City Name')
    LongitudeLatitudeCity NameDirectionSurvivors
    3254.8SmolenskAdvance145000
    33.254.9DorogobougeAdvance140000
    34.455.5ChjatAdvance127100
    37.655.8MoscouAdvance100000
    34.355.2WixmaRetreat55000
    3254.6SmolenskRetreat24000
    30.454.4OrschaRetreat20000
    26.854.3MoiodexnoRetreat12000

    但是,这个方法并不修改原始表。

    minard
    LongitudeLatitudeCityDirectionSurvivors
    3254.8SmolenskAdvance145000
    33.254.9DorogobougeAdvance140000
    34.455.5ChjatAdvance127100
    37.655.8MoscouAdvance100000
    34.355.2WixmaRetreat55000
    3254.6SmolenskRetreat24000
    30.454.4OrschaRetreat20000
    26.854.3MoiodexnoRetreat12000

    常见的模式时将原始名称minard赋给新的表,以便minard未来的所有使用,都会引用修改标签的表格。

    minard = minard.relabeled('City', 'City Name')
    minard
    LongitudeLatitudeCity NameDirectionSurvivors
    3254.8SmolenskAdvance145000
    33.254.9DorogobougeAdvance140000
    34.455.5ChjatAdvance127100
    37.655.8MoscouAdvance100000
    34.355.2WixmaRetreat55000
    3254.6SmolenskRetreat24000
    30.454.4OrschaRetreat20000
    26.854.3MoiodexnoRetreat12000

    访问列中的数据

    我们可以使用列标签来访问列中的数据数组。

    minard.column('Survivors')
    array([145000, 140000, 127100, 100000,  55000,  24000,  20000,  12000])

    五列的下标分别为0, 1, 2, 3, 4Survivors列也可以使用列下标来访问。

    minard.column(4)
    array([145000, 140000, 127100, 100000,  55000,  24000,  20000,  12000])

    数组中的八个条目下标为0, 1, 2, ..., 7。列中的条目可以使用item访问,就像任何数组那样。

    minard.column(4).item(0)
    145000
    minard.column(4).item(5)
    24000

    处理列中的数据

    因为列是数组,所以我们可以使用数组操作来探索新的信息。 例如,我们可以创建一个新列,其中包含 Smolensk 之后每个城市的所有幸存者的百分比。

    initial = minard.column('Survivors').item(0)
    minard = minard.with_columns(
        'Percent Surviving', minard.column('Survivors')/initial
    )
    minard
    LongitudeLatitudeCity NameDirectionSurvivorsPercent Surviving
    3254.8SmolenskAdvance145000100.00%
    33.254.9DorogobougeAdvance14000096.55%
    34.455.5ChjatAdvance12710087.66%
    37.655.8MoscouAdvance10000068.97%
    34.355.2WixmaRetreat5500037.93%
    3254.6SmolenskRetreat2400016.55%
    30.454.4OrschaRetreat2000013.79%
    26.854.3MoiodexnoRetreat120008.28%

    要使新列中的比例显示为百分比,我们可以使用选项PercentFormatter调用set_format方法。 set_format方法接受Formatter对象,存在日期(DateFormatter),货币(CurrencyFormatter),数字和百分比。

    minard.set_format('Percent Surviving', PercentFormatter)
    LongitudeLatitudeCity NameDirectionSurvivorsPercent Surviving
    3254.8SmolenskAdvance145000100.00%
    33.254.9DorogobougeAdvance14000096.55%
    34.455.5ChjatAdvance12710087.66%
    37.655.8MoscouAdvance10000068.97%
    34.355.2WixmaRetreat5500037.93%
    3254.6SmolenskRetreat2400016.55%
    30.454.4OrschaRetreat2000013.79%
    26.854.3MoiodexnoRetreat120008.28%

    选择列的集合

    select方法创建一个新表,仅仅包含指定的列。

    minard.select('Longitude', 'Latitude')
    LongitudeLatitude
    3254.8
    33.254.9
    34.455.5
    37.655.8
    34.355.2
    3254.6
    30.454.4
    26.854.3

    使用列索引而不是标签,也可以执行相同选择。

    minard.select(0, 1)
    LongitudeLatitude
    3254.8
    33.254.9
    34.455.5
    37.655.8
    34.355.2
    3254.6
    30.454.4
    26.854.3

    select的结果是个新表,即使当你选择一列时也是这样。

    minard.select('Survivors')
    Survivors
    145000
    140000
    127100
    100000
    55000
    24000
    20000
    12000

    要注意结果是个表格,不像column的结果,它是个数组。

    minard.column('Survivors')
    array([145000, 140000, 127100, 100000,  55000,  24000,  20000,  12000])

    另一种创建新表,包含列集合的方式,是drop你不想要的列。

    minard.drop('Longitude', 'Latitude', 'Direction')
    City NameSurvivorsPercent Surviving
    Smolensk145000100.00%
    Dorogobouge14000096.55%
    Chjat12710087.66%
    Moscou10000068.97%
    Wixma5500037.93%
    Smolensk2400016.55%
    Orscha2000013.79%
    Moiodexno120008.28%

    selectdrop都不修改原始表格。 相反,他们创建了共享相同数据的新小型表格。 保留的原始表格是实用的! 你可以生成多个不同的表格,只考虑某些列,而不用担心会互相影响。

    minard
    LongitudeLatitudeCity NameDirectionSurvivorsPercent Surviving
    3254.8SmolenskAdvance145000100.00%
    33.254.9DorogobougeAdvance14000096.55%
    34.455.5ChjatAdvance12710087.66%
    37.655.8MoscouAdvance10000068.97%
    34.355.2WixmaRetreat5500037.93%
    3254.6SmolenskRetreat2400016.55%
    30.454.4OrschaRetreat2000013.79%
    26.854.3MoiodexnoRetreat120008.28%

    我们用过的所有方法都可以用在任何表格上。

    对行排序

    CNN 在 2016 年 3 月报道说:“NBA 是全球薪水最高的职业体育联盟。”nba_salaries包含了 2015~2016 年间所有 NBA 球员的薪水。

    每行表示一个球员。列为:

    列标签描述
    PLAYER球员名称
    POSITION球员在队里的位置
    TEAM队的明确
    '15-'16 SALARY2015~2016 年的球员薪水,单位是百万美元。

    位置代码是 PG(控球后卫),SG(得分后卫),PF(大前锋),SF(小前锋)和 C(中锋)。 但接下来的内容并不涉及篮球运动的细节。

    第一行显示,亚特兰大老鹰队(Atlanta Hawks)的大前锋保罗·米尔萨普(Paul Millsap)在 2015~2016 年间的薪水接近 1870 万美元。

    # This table can be found online: https://www.statcrunch.com/app/index.php?dataid=1843341
    nba_salaries = Table.read_table('nba_salaries.csv')
    nba_salaries
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Paul MillsapPFAtlanta Hawks18.6717
    Al HorfordCAtlanta Hawks12
    Tiago SplitterCAtlanta Hawks9.75625
    Jeff TeaguePGAtlanta Hawks8
    Kyle KorverSGAtlanta Hawks5.74648
    Thabo SefoloshaSFAtlanta Hawks4
    Mike ScottPFAtlanta Hawks3.33333
    Kent BazemoreSFAtlanta Hawks2
    Dennis SchroderPGAtlanta Hawks1.7634
    Tim Hardaway Jr.SGAtlanta Hawks1.30452

    (省略了 407 行)

    该表包含 417 行,每个球员一行。 只显示了 10 行。show方法允许我们指定行数,缺省值(没有指定)是表的所有行。

    nba_salaries.show(3)
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Paul MillsapPFAtlanta Hawks18.6717
    Al HorfordCAtlanta Hawks12
    Tiago SplitterCAtlanta Hawks9.75625

    (省略了 414 行)

    通过浏览大约 20 行左右,你会看到行按字母顺序排列。 也可以使用sort方法,按球员姓名的字母顺序列出相同的行。 sort的参数是列标签或索引。

    nba_salaries.sort('PLAYER').show(5)
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Aaron BrooksPGChicago Bulls2.25
    Aaron GordonPFOrlando Magic4.17168
    Aaron HarrisonSGCharlotte Hornets0.525093
    Adreian PaynePFMinnesota Timberwolves1.93884
    Al HorfordCAtlanta Hawks12

    (省略了 412 行)

    440/5000
    为了检查球员的薪水,如果数据是按薪水排序的话,会更有帮助。

    为了实现它,我们首先简化薪水列的标签(只是为了方便),然后用新的标签SALARY进行排序。

    这会按照薪水的升序排列表中的所有行,最低的薪水在最前面。 输出是一个新表,列与原始表格相同,但行是重新排列的。

    nba = nba_salaries.relabeled("'15-'16 SALARY", 'SALARY')
    nba.sort('SALARY')
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Thanasis AntetokounmpoSFNew York Knicks0.030888
    Jordan McRaeSGPhoenix Suns0.049709
    Cory JeffersonPFPhoenix Suns0.049709
    Elliot WilliamsSGMemphis Grizzlies0.055722
    Orlando JohnsonSGPhoenix Suns0.055722
    Phil PresseyPGPhoenix Suns0.055722
    Keith ApplingPGOrlando Magic0.061776
    Sean KilpatrickSGDenver Nuggets0.099418
    Erick GreenPGUtah Jazz0.099418
    Jeff AyresPFLos Angeles Clippers0.111444

    (省略了 407 行)

    这些数字有些难以比较,因为这些球员中的一些,在赛季中改变了球队,并从不止一支球队获得了薪水。 只有最后一支球队的薪水出现在表中。 控球后卫菲尔·普莱西(Phil Pressey)在年内从费城搬到了凤凰城,可能会再次转到金州勇士队(Golden State Warriors)。

    CNN 的报道是薪酬水平的另一端 - 那些在世界上薪水最高的球员。

    为了按照薪水的降序对表格的行排序,我们必须以descending=True调用sort函数。

    nba.sort('SALARY', descending=True)
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Kobe BryantSFLos Angeles Lakers25
    Joe JohnsonSFBrooklyn Nets24.8949
    LeBron JamesSFCleveland Cavaliers22.9705
    Carmelo AnthonySFNew York Knicks22.875
    Dwight HowardCHouston Rockets22.3594
    Chris BoshPFMiami Heat22.1927
    Chris PaulPGLos Angeles Clippers21.4687
    Kevin DurantSFOklahoma City Thunder20.1586
    Derrick RosePGChicago Bulls20.0931
    Dwyane WadeSGMiami Heat20

    (省略了 407 行)

    科比(Kobe Bryant)在湖人队(Lakers)的最后一个赛季是薪水最高的,2500 万美元。 请注意,MVP 斯蒂芬·库里(Stephen Curry)并没有出现在前 10 名之列。他排在后面,我们将在后面看到。

    具名参数

    这个调用表达式的descending = True部分称为具名参数。 调用一个函数或方法时,每个参数都有一个位置和一个名字。 从函数或方法的帮助文本中都可以看出它们。

    help(nba.sort)
    Help on method sort in module datascience.tables:
    
    sort(column_or_label, descending=False, distinct=False) method of datascience.tables.Table instance
        Return a Table of rows sorted according to the values in a column.
    
        Args:
            ``column_or_label``: the column whose values are used for sorting.
    
            ``descending``: if True, sorting will be in descending, rather than
                ascending order.
    
            ``distinct``: if True, repeated values in ``column_or_label`` will
                be omitted.
    
        Returns:
            An instance of ``Table`` containing rows sorted based on the values
            in ``column_or_label``.
    
        >>> marbles = Table().with_columns(
        ...    "Color", make_array("Red", "Green", "Blue", "Red", "Green", "Green"),
        ...    "Shape", make_array("Round", "Rectangular", "Rectangular", "Round", "Rectangular", "Round"),
        ...    "Amount", make_array(4, 6, 12, 7, 9, 2),
        ...    "Price", make_array(1.30, 1.30, 2.00, 1.75, 1.40, 1.00))
        >>> marbles
        Color | Shape       | Amount | Price
        Red   | Round       | 4      | 1.3
        Green | Rectangular | 6      | 1.3
        Blue  | Rectangular | 12     | 2
        Red   | Round       | 7      | 1.75
        Green | Rectangular | 9      | 1.4
        Green | Round       | 2      | 1
        >>> marbles.sort("Amount")
        Color | Shape       | Amount | Price
        Green | Round       | 2      | 1
        Red   | Round       | 4      | 1.3
        Green | Rectangular | 6      | 1.3
        Red   | Round       | 7      | 1.75
        Green | Rectangular | 9      | 1.4
        Blue  | Rectangular | 12     | 2
        >>> marbles.sort("Amount", descending = True)
        Color | Shape       | Amount | Price
        Blue  | Rectangular | 12     | 2
        Green | Rectangular | 9      | 1.4
        Red   | Round       | 7      | 1.75
        Green | Rectangular | 6      | 1.3
        Red   | Round       | 4      | 1.3
        Green | Round       | 2      | 1
        >>> marbles.sort(3) # the Price column
        Color | Shape       | Amount | Price
        Green | Round       | 2      | 1
        Red   | Round       | 4      | 1.3
        Green | Rectangular | 6      | 1.3
        Green | Rectangular | 9      | 1.4
        Red   | Round       | 7      | 1.75
        Blue  | Rectangular | 12     | 2
        >>> marbles.sort(3, distinct = True)
        Color | Shape       | Amount | Price
        Green | Round       | 2      | 1
        Red   | Round       | 4      | 1.3
        Green | Rectangular | 9      | 1.4
        Red   | Round       | 7      | 1.75
        Blue  | Rectangular | 12     | 2

    help文本的最上面,出现了sort方法的签名。

    sort(column_or_label, descending=False, distinct=False)

    这描述了sort的三个参数的位置,名称和默认值。 调用此方法时,可以使用位置参数或具名参数,因此以下三个调用完全相同。

    sort('SALARY', True)
    sort('SALARY', descending=True)
    sort(column_or_label='SALARY', descending=True)

    当一个参数只是TrueFalse时,包含参数名称是实用的约定,以便更明显地说明参数值的含义。

    行的选取

    通常,我们只想提取那些行,它们对应具有特定特征的条目。 例如,我们可能只需要对应勇士的行,或者获得超过一千万美元的球员。 或者我们可能只想要薪水前五名的人。

    指定行

    Table的方法就是干这个的 - 它需要一组指定的行。 它的参数是行索引或索引数组,它创建一个只包含这些行的新表。

    例如,如果我们只想要nba的第一行,我们可以这样使用take

    nba
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Paul MillsapPFAtlanta Hawks18.6717
    Al HorfordCAtlanta Hawks12
    Tiago SplitterCAtlanta Hawks9.75625
    Jeff TeaguePGAtlanta Hawks8
    Kyle KorverSGAtlanta Hawks5.74648
    Thabo SefoloshaSFAtlanta Hawks4
    Mike ScottPFAtlanta Hawks3.33333
    Kent BazemoreSFAtlanta Hawks2
    Dennis SchroderPGAtlanta Hawks1.7634
    Tim Hardaway Jr.SGAtlanta Hawks1.30452

    (省略了 407 行)

    nba.take(0)
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Paul MillsapPFAtlanta Hawks18.6717

    这是一个新表,只拥有我们指定的单个行。

    通过指定一系列索引作为参数,我们还可以获得第四,第五和第六行。

    nba.take(np.arange(3, 6))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Jeff TeaguePGAtlanta Hawks8
    Kyle KorverSGAtlanta Hawks5.74648
    Thabo SefoloshaSFAtlanta Hawks4

    如果我们想要前五个最高薪球员的表格,我们可以先按薪水排序,然后取前五行:

    nba.sort('SALARY', descending=True).take(np.arange(5))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Kobe BryantSFLos Angeles Lakers25
    Joe JohnsonSFBrooklyn Nets24.8949
    LeBron JamesSFCleveland Cavaliers22.9705
    Carmelo AnthonySFNew York Knicks22.875
    Dwight HowardCHouston Rockets22.3594

    对应指定特征的行

    更常见的情况是,我们打算访问一组行中的数据,它们具有某种特征,但是我们并不知道其索引。 例如,我们可能想要所有薪水大于一千万美元的球员的数据,但我们不希望花费时间,对已排序的表中的行进行计数。

    where方法可以做到。 它的输出是一个表格,列与原始表格相同,但只有特征出现的行。

    where的第一个参数是列标签,列中包含信息,有关某行是否具有我们想要的特征。 如果特征是“薪水超过一千万美元”,那么列就是SALARY

    where的第二个参数是用于指定特征的方式。 一些例子会使指定的一般方式更容易理解。

    在第一个例子中,我们提取了所有薪水超过一千万美元的人的数据。

    nba.where('SALARY', are.above(10))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Paul MillsapPFAtlanta Hawks18.6717
    Al HorfordCAtlanta Hawks12
    Joe JohnsonSFBrooklyn Nets24.8949
    Thaddeus YoungPFBrooklyn Nets11.236
    Al JeffersonCCharlotte Hornets13.5
    Nicolas BatumSGCharlotte Hornets13.1253
    Kemba WalkerPGCharlotte Hornets12
    Derrick RosePGChicago Bulls20.0931
    Jimmy ButlerSGChicago Bulls16.4075
    Joakim NoahCChicago Bulls13.4

    (省略了 59 行)

    are.above(10)的参数的确保了,每个选择的行的SALARY大于 10。

    新的表格有 69 行,相当于 69 个球员的薪水是一千万美元。 按顺序排列这些行使数据更易于分析。 多伦多猛龙队(Toronto Raptors)的德玛尔·德罗赞(DeMar DeRozan)是这个分组(薪水超过一千万美元)中“最穷”的一个。

    nba.where('SALARY', are.above(10)).sort('SALARY')
    PLAYERPOSITIONTEAM'15-'16 SALARY
    DeMar DeRozanSGToronto Raptors10.05
    Gerald WallaceSFPhiladelphia 76ers10.1059
    Luol DengSFMiami Heat10.1516
    Monta EllisSGIndiana Pacers10.3
    Wilson ChandlerSFDenver Nuggets10.4494
    Brendan HaywoodCCleveland Cavaliers10.5225
    Jrue HolidayPGNew Orleans Pelicans10.5955
    Tyreke EvansSGNew Orleans Pelicans10.7346
    Marcin GortatCWashington Wizards11.2174
    Thaddeus YoungPFBrooklyn Nets11.236

    (省略了 59 行)

    斯蒂芬·库里(Stephen Curry)挣了多少? 对于答案,我们必须访问PLAYER的值等于Stephen Curry的行。 这是一个只包含一行的表格:

    nba.where('PLAYER', are.equal_to('Stephen Curry'))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Stephen CurryPGGolden State Warriors11.3708

    库里只有不到 1140 万美元。 这是很多钱,但还不到勒布朗·詹姆斯(LeBron James)薪水的一半。 你可以在本节前面的“前 5 名”表中找到薪水,或者你可以在上面的代码中找到它,将'Stephen Curry换成'LeBron James'

    代码中再次使用了are,但这次是谓词equal_to而不是上面那个。 因此,例如,你可以得到包含所有的勇士的表格:

    nba.where('TEAM', are.equal_to('Golden State Warriors')).show()
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Klay ThompsonSGGolden State Warriors15.501
    Draymond GreenPFGolden State Warriors14.2609
    Andrew BogutCGolden State Warriors13.8
    Andre IguodalaSFGolden State Warriors11.7105
    Stephen CurryPGGolden State Warriors11.3708
    Jason ThompsonPFGolden State Warriors7.00847
    Shaun LivingstonPGGolden State Warriors5.54373
    Harrison BarnesSFGolden State Warriors3.8734
    Marreese SpeightsCGolden State Warriors3.815
    Leandro BarbosaSGGolden State Warriors2.5
    Festus EzeliCGolden State Warriors2.00875
    Brandon RushSFGolden State Warriors1.27096
    Kevon LooneySFGolden State Warriors1.13196
    Anderson VarejaoPFGolden State Warriors0.289755

    这部分表格已经按薪水排序,因为原始的表格按薪水排序,列出了同一个球队中球员。 行尾的.show()确保显示所有行,而不仅仅是前 10 行。

    请求某列等于某值的行非常普遍,因此are.equal_to调用是可选的。 相反,仅仅使用列名和值来调用where方法,以达到相同的效果。

    nba.where('TEAM', 'Denver Nuggets') 
    # equivalent to nba.where('TEAM', are.equal_to('Denver Nuggets'))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Danilo GallinariSFDenver Nuggets14
    Kenneth FariedPFDenver Nuggets11.236
    Wilson ChandlerSFDenver Nuggets10.4494
    JJ HicksonCDenver Nuggets5.6135
    Jameer NelsonPGDenver Nuggets4.345
    Will BartonSFDenver Nuggets3.53333
    Emmanuel MudiayPGDenver Nuggets3.10224
    Darrell ArthurPFDenver Nuggets2.814
    Jusuf NurkicCDenver Nuggets1.842
    Joffrey LauvergneCDenver Nuggets1.70972

    (省略了 4 行)

    多个属性

    通过重复使用where,你可以访问具有多个指定特征的行。 例如,这是一种方法,提取薪水超过一千五百万美元的所有控球后卫。

    nba.where('POSITION', 'PG').where('SALARY', are.above(15))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Derrick RosePGChicago Bulls20.0931
    Kyrie IrvingPGCleveland Cavaliers16.4075
    Chris PaulPGLos Angeles Clippers21.4687
    Russell WestbrookPGOklahoma City Thunder16.7442
    John WallPGWashington Wizards15.852

    一般形式

    现在你已经意识到,通过选择具有给定特征的行,来创建新表的一般方法,是使用whereare,以及适当的条件:

    original_table_name.where(column_label_string, are.condition)
    
    nba.where('SALARY', are.between(10, 10.3))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Luol DengSFMiami Heat10.1516
    Gerald WallaceSFPhiladelphia 76ers10.1059
    Danny GreenSGSan Antonio Spurs10
    DeMar DeRozanSGToronto Raptors10.05

    请注意,上面的表格包括赚一千万美元的 Danny Green,而不包括一千三百万美元的 Monta Ellis。 与 Python 中的其他地方一样,范围包括左端但不包括右端。

    如果我们指定一个任何行都不满足的条件,我们得到一个带有列标签但没有行的表。

    nba.where('PLAYER', are.equal_to('Barack Obama'))
    PLAYERPOSITIONTEAMSALARY

    更多条件

    这里有一些谓词,你可能会觉得有用。 请注意,xy是数字,STRING是一个字符串,Z是数字或字符串;你必须指定这些,取决于你想要的特征。

    谓词描述
    are.equal_to(Z)等于Z
    are.above(x)大于x
    are.above_or_equal_to(x)大于等于x
    are.below(x)小于x
    are.below_or_equal_to(x)小于等于x
    are.between(x, y)大于等于x,小于y
    are.strictly_between(x, y)大于x,小于y
    are.between_or_equal_to(x, y)大于等于x,小于等于y
    are.containing(S)包含字符串S

    你也可以指定任何这些条件的否定,通过在条件前面使用.not_

    谓词描述
    are.not_equal_to(Z)不等于Z
    are.not_above(x)不大于x

    以及其他。通常的逻辑规则是适用的,例如,“不大于x”等价于“小于等于x”。

    我们以一系列示例结束这一节。

    are.containing的使用有助于少打一些字。 例如,你可以指定Warriors而不是Golden State Warriors

    nba.where('TEAM', are.containing('Warriors')).show()
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Klay ThompsonSGGolden State Warriors15.501
    Draymond GreenPFGolden State Warriors14.2609
    Andrew BogutCGolden State Warriors13.8
    Andre IguodalaSFGolden State Warriors11.7105
    Stephen CurryPGGolden State Warriors11.3708
    Jason ThompsonPFGolden State Warriors7.00847
    Shaun LivingstonPGGolden State Warriors5.54373
    Harrison BarnesSFGolden State Warriors3.8734
    Marreese SpeightsCGolden State Warriors3.815
    Leandro BarbosaSGGolden State Warriors2.5
    Festus EzeliCGolden State Warriors2.00875
    Brandon RushSFGolden State Warriors1.27096
    Kevon LooneySFGolden State Warriors1.13196
    Anderson VarejaoPFGolden State Warriors0.289755

    你可以提取所有后卫的数据,包括控球后卫和得分后卫。

    nba.where('POSITION', are.containing('G'))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Jeff TeaguePGAtlanta Hawks8
    Kyle KorverSGAtlanta Hawks5.74648
    Dennis SchroderPGAtlanta Hawks1.7634
    Tim Hardaway Jr.SGAtlanta Hawks1.30452
    Jason RichardsonSGAtlanta Hawks0.947276
    Lamar PattersonSGAtlanta Hawks0.525093
    Terran PettewaySGAtlanta Hawks0.525093
    Avery BradleyPGBoston Celtics7.73034
    Isaiah ThomasPGBoston Celtics6.91287
    Marcus SmartPGBoston Celtics3.43104

    (省略了 171 行)

    你可以获取所有不是克利夫兰骑士队的球员,并且薪水不低于两千万美元:

    other_than_Cavs = nba.where('TEAM', are.not_equal_to('Cleveland Cavaliers'))
    other_than_Cavs.where('SALARY', are.not_below(20))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Joe JohnsonSFBrooklyn Nets24.8949
    Derrick RosePGChicago Bulls20.0931
    Dwight HowardCHouston Rockets22.3594
    Chris PaulPGLos Angeles Clippers21.4687
    Kobe BryantSFLos Angeles Lakers25
    Chris BoshPFMiami Heat22.1927
    Dwyane WadeSGMiami Heat20
    Carmelo AnthonySFNew York Knicks22.875
    Kevin DurantSFOklahoma City Thunder20.1586

    有很多方式可以创建相同的表格。这里是另一种,并且显然你可以想出来更多。

    other_than_Cavs.where('SALARY', are.above_or_equal_to(20))
    PLAYERPOSITIONTEAM'15-'16 SALARY
    Joe JohnsonSFBrooklyn Nets24.8949
    Derrick RosePGChicago Bulls20.0931
    Dwight HowardCHouston Rockets22.3594
    Chris PaulPGLos Angeles Clippers21.4687
    Kobe BryantSFLos Angeles Lakers25
    Chris BoshPFMiami Heat22.1927
    Dwyane WadeSGMiami Heat20
    Carmelo AnthonySFNew York Knicks22.875
    Kevin DurantSFOklahoma City Thunder20.1586

    你可以看到,where的使用提供了很大的灵活性,来访问你感兴趣的特征。 不要犹豫,尝试它吧!

    示例:人口趋势

    美国人口的趋势

    现在我们做好了处理大量的数据表的准备。 下面的文件包含“美国居民人口的年度估计,按年龄和性别分列”。 请注意,read_table可以直接从 URL 读取数据。

    # As of Jan 2017, this census file is online here: 
    data = 'http://www2.census.gov/programs-surveys/popest/datasets/2010-2015/national/asrh/nc-est2015-agesex-res.csv'
    
    # A local copy can be accessed here in case census.gov moves the file:
    # data = 'nc-est2015-agesex-res.csv'
    
    full_census_table = Table.read_table(data)
    full_census_table
    SEXAGECENSUS2010POPESTIMATESBASE2010POPESTIMATE2010POPESTIMATE2011POPESTIMATE2012POPESTIMATE2013POPESTIMATE2014POPESTIMATE2015
    0039441533944160395133039630873926540393114139497753978038
    0139780703978090395788839665513977939394287239497763968564
    0240969294096939409086239715653980095399272039596643966583
    0341190404119051411192041024703983157399273440070793974061
    0440631704063186407755141222944112849399444940057164020035
    0540568584056872406465340877094132242412362640069004018158
    0640663814066412407301340749934097605414291641359304019207
    0740305794030594404304640832254084913410834941553264148360
    0840464864046497402560440532034093177409571141209034167887
    0941483534148369412541540357104063152410407241083494133564

    (已省略 296 行)

    只显示了表格的前 10 行。稍后我们将看到如何显示整个表格;但是,这通常不适用于大型表格。

    表格的描述一起出现。SEX列包含数字代码:0代表总体,1代表男性,2代表女性。 AGE列包含完整年份为单位的年龄,但特殊值999是人口的总和。 其余的列包含美国人口的估计。

    通常,公共表格将包含更多的信息,不仅仅是特定调查或分析所需的信息。 在这种情况下,我们假设我们只对 2010 年到 2014 年的人口变化感兴趣。让我们选择相关的列。

    partial_census_table = full_census_table.select('SEX', 'AGE', 'POPESTIMATE2010', 'POPESTIMATE2014')
    partial_census_table
    SEXAGEPOPESTIMATE2010POPESTIMATE2014
    0039513303949775
    0139578883949776
    0240908623959664
    0341119204007079
    0440775514005716
    0540646534006900
    0640730134135930
    0740430464155326
    0840256044120903
    0941254154108349

    (已省略 296 行)

    我们也可以简化所选列的标签。

    us_pop = partial_census_table.relabeled('POPESTIMATE2010', '2010').relabeled('POPESTIMATE2014', '2014')
    us_pop
    SEXAGE20102014
    0039513303949775
    0139578883949776
    0240908623959664
    0341119204007079
    0440775514005716
    0540646534006900
    0640730134135930
    0740430464155326
    0840256044120903
    0941254154108349

    (已省略 296 行)

    我们现在有了一个易于使用的表格。 表中的每一列都是一个等长的数组,因此列可以使用算术进行组合。 这是 2010 年至 2014 年的人口变化。

    us_pop.column('2014') - us_pop.column('2010')
    array([  -1555,   -8112, -131198, ...,    6443,   12950, 4693244])

    让我们使用包含这些变化的列来扩展us_pop,一列是绝对数值,另一列是相对于 2010 年数值的百分比。

    change = us_pop.column('2014') - us_pop.column('2010')
    census = us_pop.with_columns(
        'Change', change,
        'Percent Change', change/us_pop.column('2010')
    )
    census.set_format('Percent Change', PercentFormatter)
    SEXAGE20102014ChangePercent Change
    0039513303949775-1555-0.04%
    0139578883949776-8112-0.20%
    0240908623959664-131198-3.21%
    0341119204007079-104841-2.55%
    0440775514005716-71835-1.76%
    0540646534006900-57753-1.42%
    0640730134135930629171.54%
    07404304641553261122802.78%
    0840256044120903952992.37%
    0941254154108349-17066-0.41%

    (已省略 296 行)

    将数据排序。让我们按照人口绝对变化的降序排序表格。

    census.sort('Change', descending=True)
    SEXAGE20102014ChangePercent Change
    099930934686331890740195605383.09%
    199915208804315695533748672943.20%
    299915725882016195206446932442.98%
    0672693707348524179153429.38%
    0642706055348755978150428.88%
    0662621335334706072572527.69%
    0652678525338282470429926.29%
    0711953607251970556609828.98%
    0343822189436474854255914.19%
    0234217228470215648492811.50%

    毫不奇怪,排序后表格的第一行对应整个人口:所有年龄和性别的分组。 从 2010 年到 2014 年,美国人口增加了约 950 万人,仅为 3%。

    接下来的两行分别对应所有的男性和所有的女性。 以绝对数量和百分比来衡量,男性人口的增长高于女性人口。 百分比变化都在 3% 左右。

    现在看看接下来的几行。 百分比变化从总人口的 3% 左右,上升到 60 年代末和 70 年代初的近 30%。 这个惊人的变化称为美国的老龄化。

    到目前为止,2014 年 64~67 岁年龄段的绝对变化最大。什么可以解释这一大幅增长的原因? 我们可以通过考察相关分组的出生年份,来探索这个问题。

    那些 2010 年在 64~67 岁年龄段的人,在 1943 年到 1946 年间出生。珍珠港的袭击是在 1941 年底,美军在 1942 年发动了一场大规模战争,结束于 1945 年。

    2014 年 64 岁到 67 岁的人生于 1947 年到 1950 年,是美国二战后的生育高峰。

    战后的生育高峰,是我们观察到的巨大变化的主要原因。

    示例:性别趋势

    性别比例的趋势

    我们现在拥有了足够的编码技能,足以检查美国人群中的特征和趋势。在这个例子中,我们将查看不同年龄的男性和女性的分布情况。我们将继续使用上一节中的us_pop表。

    us_pop
    SEXAGE20102014
    0039513303949775
    0139578883949776
    0240908623959664
    0341119204007079
    0440775514005716
    0540646534006900
    0640730134135930
    0740430464155326
    0840256044120903
    0941254154108349

    (已省略 296 行)

    我们从之前对这个数据集的检查得知,表格的描述一起出现。 它提醒了表中包含的内容。

    每一行表示一个年龄。SEX列包含数字代码:0代表总数,1代表男性,2代表女性。 年龄栏包含以完整年份为单位的年龄,但特殊值999代表整个人口,不论年龄是什么。其余的列包含美国人口的估计。

    理解AGE=100

    作为一个初步的例子,我们来解释一下表中最后的年龄的数据,其中年龄是100岁。下面的代码提取了男性和女性(性别代码0)的组合分组,年龄最大的行。

    us_pop.where('SEX', are.equal_to(0)).where('AGE', are.between(97, 101))
    SEXAGE20102014
    0976889383089
    0984703759726
    0993217841468
    01005441071626

    不足为奇的是,年龄越大,人数越少,例如 99 岁的人数少于 98 岁。

    然而,令人吃惊的是,100 岁的数值比 99 岁的数值要大得多。仔细查看文件,这是因为人口普查局使用 100 作为 100 或岁以上的每个人的代码。

    年龄为 100 岁的人不仅仅代表 100 岁的人,还包括年龄在 100 岁以上的人。这就是为什么那一行的数字大于 99 岁的人的数字。

    男性和女性的整体比例

    我们现在开始考察 2014 年的性别比例。首先,我们一起来看看所有的年龄。 请记住,这意味着查看AGE编码为999的行。all_ages表包含此信息。其中有三行:一个是两种性别总体,一个是男性(SEX代码为1),一个是女性(SEX代码为2)。

    us_pop_2014 = us_pop.drop('2010')
    all_ages = us_pop_2014.where('AGE', are.equal_to(999))
    all_ages
    SEXAGE2014
    0999318907401
    1999156955337
    2999161952064

    all_ages的第 0 行包含两年中每年的美国总人口。2014 年,美国人口刚刚少于 3.19 亿。

    第 1 行包含男性的计数,女性是第 2 行。 比较这两行可以看到,在 2014 年,美国的女性比男性多。

    第 1 行和第 2 行的人口数加起来为第 0 行的总人口数。

    为了与其他数量进行比较,我们需要将这些数字转换为总人口中的百分比。 让我们访问 2014 年的总数并命名。 然后,我们将显示带有比例列的人口表格。 与我们先前观察到的,女性人数多于男性的情况一致,2014 年约有 50.8% 的人口是女性,两年的每年中,约有 49.2% 的人口是男性。

    pop_2014 = all_ages.column('2014').item(0)
    all_ages.with_column(
        'Proportion', all_ages.column('2014')/pop_2014
    ).set_format('Proportion', PercentFormatter)
    SEXAGE2014Proportion
    0999318907401100.00%
    199915695533749.22%
    299916195206450.78%

    新生儿中男孩和女孩的比例

    但是,当我们查看婴儿时,情况正好相反。 让我们将婴儿定义为还没有完整一年的人,对应年龄为 0 的行。这里是他们的人口数量。 你可以看到男婴比女婴多。

    infants = us_pop_2014.where('AGE', are.equal_to(0))
    infants
    SEXAGE2014
    003949775
    102020326
    201929449

    像以前一样,我们可以将这些数字转换成婴儿总数中的百分比。 所得表格显示,2014 年,美国超过 51% 的婴儿是男性。

    infants_2014 = infants.column('2014').item(0)
    infants.with_column(
        'Proportion', infants.column('2014')/infants_2014
    ).set_format('Proportion', PercentFormatter)
    SEXAGE2014Proportion
    003949775100.00%
    10202032651.15%
    20192944948.85%

    事实上,长期以来,新生儿中男孩的比例略高于 1/2。 这个原因还没有得到彻底的理解,科学家们还在努力

    每个年龄的男女比例

    我们已经看到,虽然男婴比女婴多,但女性总数比男性多。 所以很显然,性别之间的分隔在不同年龄之间必须有所不同。

    为了研究这个变化,我们将女性和男性的数据分开,并消除所有年龄的组合,年龄编码为 999 的行。

    femalesmale表格分别包含两个性别的数据。

    females_all_rows = us_pop_2014.where('SEX', are.equal_to(2))
    females = females_all_rows.where('AGE', are.not_equal_to(999))
    females
    SEXAGE2014
    201929449
    211931375
    221935991
    231957483
    241961199
    251962561
    262024870
    272032494
    282015285
    292010659

    (省略了 91 行)

    males_all_rows = us_pop_2014.where('SEX', are.equal_to(1))
    males = males_all_rows.where('AGE', are.not_equal_to(999))
    males
    SEXAGE2014
    102020326
    112018401
    122023673
    132049596
    142044517
    152044339
    162111060
    172122832
    182105618
    192097690

    (省略了 91 行)

    现在的计划是,比较两年中每一年的,每个年龄的女性人数和男性人数。 数组和表格的方法为我们提供了直接的方式。 这两个表格中,每个年龄都有一行。

    males.column('AGE')
    array([  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,
            13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,
            26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,
            39,  40,  41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,
            52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  64,
            65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,
            78,  79,  80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,
            91,  92,  93,  94,  95,  96,  97,  98,  99, 100])
    females.column('AGE')
    array([  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,
            13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,
            26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,
            39,  40,  41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,
            52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  64,
            65,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,
            78,  79,  80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,
            91,  92,  93,  94,  95,  96,  97,  98,  99, 100])

    对于任何特定年龄,我们都可以通过将女性人数除以男性人数,获的女性:男性性别比例。 为了一步完成它,我们可以使用列来提取女性数量的数组,和相应的男性数量的数组,然后简单地将一个数组除以另一个数组。 逐元素相除将为所有年份创建性别比例的数组。

    ratios = Table().with_columns(
        'AGE', females.column('AGE'),
        '2014 F:M RATIO', females.column('2014')/males.column('2014')
    )
    ratios
    AGE2014 F:M RATIO
    00.955019
    10.956884
    20.956672
    30.955058
    40.959248
    50.959998
    60.959172
    70.957445
    80.957099
    90.958511

    (省略了 91 行)

    从输出中可以看出,九岁以下儿童的比例都在 0.96 左右。 当男女比例小于 1 时,女性比男性少。 因此,我们所看到的是,在 0~9 岁的年龄段中,女孩比男孩少。此外,在每个年龄中,每 100 个男孩大约对应 96 个女孩。

    那么人口中女性的整体比例为什么高于男性呢?

    当我们检查年龄的另一端时,会发现一些非同寻常的事情。 以下是 75 岁以上的男女比例。

    ratios.where('AGE', are.above(75)).show()
    AGE2014 F:M RATIO
    761.23487
    771.25797
    781.28244
    791.31627
    801.34138
    811.37967
    821.41932
    831.46552
    841.52048
    851.5756
    861.65096
    871.72172
    881.81223
    891.91837
    902.01263
    912.09488
    922.2299
    932.33359
    942.52285
    952.67253
    962.87998
    973.09104
    983.41826
    993.63278
    1004.25966

    不仅所有这些比例大于 1,在所有这些年龄段中,女性比男性多,其中许多比例大于 1。

    • 在 89 岁和 90 岁中,比例接近 2,这意味着 2014 年这些年龄的女性约为男性的两倍。
    • 在 98 岁和 99 岁中,女性约为男性的 3.5 至 4 倍。

    如果你想知道有多少高龄的人,你可以使用 Python 来发现:

    males.where('AGE', are.between(98, 100))
    SEXAGE2014
    19813518
    1998951
    females.where('AGE', are.between(98, 100))
    SEXAGE2014
    29846208
    29932517

    下图展示了年龄相关的性别比率。 蓝色曲线显示 2014 年的比例与年龄。

    从 0 岁到 60 岁,这个比例差不多是 1(表示男性和女性差不多相等),但从 65 岁开始,比例开始急剧上升(女性多于男性)。

    美国女性人数多于男性,部分原因是老年妇女的显着的性别不平衡。

    ratios.plot('AGE')

    展开全文
  • 记得每次的语文考试中作文总是要消耗掉很大一部分的时间,先想标题,之后想文章个构思没整个过程是枯燥无味的,但是却又是必须要干的,那对于作文的类型选择上其实空间也很大,我们可以尝试换种类型来书写作文就可以...

    作文的撰写对很多人来说并不陌生,记得每次的语文考试中作文总是要消耗掉很大一部分的时间,先想标题,之后想文章个构思没整个过程是枯燥无味的,但是却又是必须要干的,那对于作文的类型选择上其实空间也很大,我们可以尝试换种类型来书写作文就可以换一种心情面对,那具体有哪些类型呢?下面是分享的作文类型总结思维导图模板以及绘制方法,希望对大家有所帮助。

    绘制工具:迅捷画图
      绘制方法:

      1.在绘制的第一步要想清楚要怎样绘制?只要能在绘制时不被打扰就可以,该主题是作文类型总结思维导图,那就分享利用迅捷画图绘制思维导图的操作方法介绍希望可以帮助到大家。
    在这里插入图片描述
      2.绘制思维导图的第二步是要怎样绘制,常见的思维导图就是像树枝一样的主干分明绘制清晰,我们可以参考这种方式,围绕主体枝干进行使用,在面板中的中心主题处可以对节点进行添加使用。
    在这里插入图片描述
      3.绘制思维导图的第三步,对添加的枝干或者节点内容机进行添加,这步就很简单了,一般是用鼠标双击节点就可以编辑内容使用。
    在这里插入图片描述
      4.绘制思维导图的第四步是,要怎样丰富思维导图,如果只是一个框架加内容也是可以使用的,但是体验效果可能会没有这么好,我们可以对思维导图的框架结构以及思维导图的框架颜色进行稍微改变,这样体验度就又不一样了。
    在这里插入图片描述
      5.绘制思维导图第五步有什么用途,我们绘制的思维导图要用来干什么?讲课?展示?还是单纯的想练手使用,这些都是需要考考虑清楚的。根据用处来判断我们需要使用什么格式的之后在选择导出格式进行使用。
    在这里插入图片描述
      按照上述绘制思维导图的五步法进行绘制使用,就可以绘制出完整精美的思维导图,这样在使用的时候也是很方便的,很容易进行绘制使用,面对这种绘制思维导图的方法,不仅让然感叹移动互联网带给我们很多便捷,希望上面的操作方法可以有所帮助。

    展开全文
  • 计算与推断思维、分类

    万次阅读 2018-01-28 00:08:33
    、分类 原文:Classification 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 David Wagner 是这一章的主要作者。 机器学习是一类技术,用于自动寻找数据中的规律,并使用它来推断或预测...

    十五、分类

    原文:Classification

    译者:飞龙

    协议:CC BY-NC-SA 4.0

    自豪地采用谷歌翻译

    David Wagner 是这一章的主要作者。

    机器学习是一类技术,用于自动寻找数据中的规律,并使用它来推断或预测。你已经看到了线性回归,这是一种机器学习技术。本章介绍一个新的技术:分类。

    分类就是学习如何根据过去的例子做出预测。我们举了一些例子,告诉我们什么是正确的预测,我们希望从这些例子中学习,如何较好地预测未来。以下是在实践中分类的一些应用领域:

    • 他们有一些每个订单的信息(例如,它的总值,订单是否被运送到这个客户以前使用过的地址,是否与信用卡持有人的账单地址相同)。他们有很多过去的订单数据,他们知道哪些过去的订单是欺诈性的,哪些不是。他们想要学习规律,这将帮助他们预测新订单到达时,这些新订单是否有欺诈行为。

    • 在线约会网站希望预测:这两个人合适吗?他们有很多数据,他们过去向顾客推荐一些东西,它们就知道了哪个是成功的。当新客户注册时,他们想预测谁可能是他们的最佳伴侣。

    • 医生想知道:这个病人是否患有癌症?根据一些实验室测试的结果,他们希望能够预测特定患者是否患有癌症。基于一些实验室测试的测量结果,以及他们是否最终发展成癌症,并且由此他们希望尝试推断,哪些测量结果倾向于癌症(或非癌症)特征,以便能够准确地诊断未来的患者。

    • 政客们想预测:你打算为他们投票吗?这将帮助他们将筹款工作集中在可能支持他们的人身上,并将动员工作集中在投票给他们的人身上。公共数据库和商业数据库有大多数人的大量信息,例如,他们是否拥有房屋或房租;他们是否住在富裕的社区还是贫穷的社区;他们的兴趣和爱好;他们的购物习惯;等等。政治团体已经调查了一些选民,并找到了他们计划投票的人,所以他们有一些正确答案已知的例子。

    所有这些都是分类任务。请注意,在每个例子中,预测是一个是与否的问题 - 我们称之为二元分类,因为只有两个可能的预测。

    在分类任务中,我们想要进行预测的每个个体或情况都称为观测值。我们通常有很多观测值。每个观测值具有多个已知属性(例如,亚马逊订单的总值,或者选民的年薪)。另外,每个观测值都有一个类别,这是对我们关心的问题(例如欺骗与否,或者是否投票)的回答。

    当亚马逊预测订单是否具有欺诈性时,每个订单都对应一个单独的观测值。每个观测值都有几个属性:订单的总值,订单是否被运送到此客户以前使用的地址等等。观测值类别为 0 或 1,其中 0 意味着订单不是欺诈,1 意味着订单是欺诈性的。当一个客户生成新的订单时,我们并没有观察到这个订单是否具有欺诈性,但是我们确实观察了这个订单的属性,并且我们会尝试用这些属性来预测它的类别。

    分类需要数据。它涉及到发现规律,并且为了发现规律,你需要数据。这就是数据科学的来源。特别是,我们假设我们可以获得训练数据:一系列的观测数据,我们知道每个观测值的类别。这些预分类的观测值集合也被称为训练集。分类算法需要分析训练集,然后提出一个分类器:用于预测未来观测值类别的算法。

    分类器不需要是完全有用的。即使准确度低于 100%,它们也可以是有用的。例如,如果在线约会网站偶尔会提出不好的建议,那没关系;他们的顾客已经预期,在他们找到真爱之前需要遇见许多人。当然,你不希望分类器犯太多的错误,但是不必每次都得到正确的答案。

    最近邻

    在本节中,我们将开发最近邻分类方法。 如果一些代码神秘,不要担心,现在只要把注意力思路上。 在本章的后面,我们将看到如何将我们的想法组织成执行分类的代码。

    慢性肾病

    我们来浏览一个例子。 我们将使用收集的数据集来帮助医生诊断慢性肾病(CKD)。 数据集中的每一行都代表单个患者,过去接受过治疗并且诊断已知。 对于每个患者,我们都有一组血液测试的测量结果。 我们希望找到哪些测量结果对诊断慢性肾病最有用,并根据他们的血液检查结果,开发一种方法,将未来的患者分类为“CKD”或“非 CKD”。

    ckd = Table.read_table('ckd.csv').relabeled('Blood Glucose Random', 'Glucose')
    ckd
    AgeBlood PressureSpecific GravityAlbuminSugarRed Blood CellsPus CellPus Cell clumpsBacteriaGlucoseBlood UreaSerum CreatinineSodiumPotassiumHemoglobinPacked Cell VolumeWhite Blood Cell CountRed Blood Cell CountHypertensionDiabetes MellitusCoronary Artery DiseaseAppetitePedal EdemaAnemiaClass
    48701.00540normalabnormalpresentnotpresent117563.81112.511.23267003.9yesnonopooryesyes1
    53901.0220abnormalabnormalpresentnotpresent701077.21143.79.529121003.7yesyesnopoornoyes1
    63701.0130abnormalabnormalpresentnotpresent380602.71314.210.83245003.8yesyesnopooryesno1
    68801.0132normalabnormalpresentpresent157904.11306.45.616110002.6yesyesyespooryesno1
    61801.01520abnormalabnormalnotpresentnotpresent1731483.91355.27.72492003.2yesyesyespooryesyes1
    48801.02540normalabnormalnotpresentnotpresent951637.71363.89.83269003.4yesnonogoodnoyes1
    69701.0134normalabnormalnotpresentnotpresent264872.7130412.53796004.1yesyesyesgoodyesno1
    73701.00500normalnormalnotpresentnotpresent70320.912541029189003.5yesyesnogoodyesno1
    73801.0220abnormalabnormalnotpresentnotpresent2531424.61385.810.53372004.3yesyesyesgoodnono1
    46601.0110normalnormalnotpresentnotpresent163923.314149.828146003.2yesyesnogoodnono1

    (省略了 148 行)

    一些变量是类别(像“异常”这样的词),还有一些是定量的。 定量变量都有不同的规模。 我们将要通过眼睛比较和估计距离,所以我们只选择一些变量并在标准单位下工作。 之后我们就不用担心每个变量的规模。

    ckd = Table().with_columns(
        'Hemoglobin', standard_units(ckd.column('Hemoglobin')),
        'Glucose', standard_units(ckd.column('Glucose')),
        'White Blood Cell Count', standard_units(ckd.column('White Blood Cell Count')),
        'Class', ckd.column('Class')
    )
    ckd
    HemoglobinGlucoseWhite Blood Cell CountClass
    -0.865744-0.221549-0.5697681
    -1.45745-0.9475971.162681
    -1.004973.84123-1.275581
    -2.814880.3963640.8097771
    -2.083950.6435290.2322931
    -1.35303-0.561402-0.5056031
    -0.4132662.049280.3606231
    -1.28342-0.9475973.344291
    -1.109391.87936-0.4093561
    -1.353030.4890511.964751

    (省略了 148 行)

    我们来看两列,(病人的血液中)血红蛋白水平和血糖水平(一天中的随机时间;没有专门为血液测试禁食)。

    我们将绘制一个散点图来显示两个变量之间的关系。 蓝点是 CKD 患者; 金点是非 CKD 的患者。 什么样的医学检验结果似乎表明了 CKD?

    color_table = Table().with_columns(
        'Class', make_array(1, 0),
        'Color', make_array('darkblue', 'gold')
    )
    ckd = ckd.join('Class', color_table)
    ckd.scatter('Hemoglobin', 'Glucose', colors='Color')

    假设爱丽丝是不在数据集中的新患者。 如果我告诉你爱丽丝的血红蛋白水平和血糖水平,你可以预测她是否有 CKD 嘛? 确实看起来可以! 你可以在这里看到非常清晰的规律:右下角的点代表没有 CKD 的人,其余的倾向于有 CKD 的人。 对于人来说,规律是显而易见的。 但是,我们如何为计算机编程来自动检测这种规律?

    最近邻分类器

    我们可能寻找很多种模式,还有很多分类算法。但是我会告诉你一个算法,它拥有令人惊讶的效果。它被称为最近邻分类。这是它的思路。如果我们有爱丽丝的血红蛋白和血糖数值,我们可以把她放在这个散点图的某个地方;血红蛋白是她的x坐标,血糖是她的y坐标。现在,为了预测她是否有 CKD,我们在散点图中找到最近的点,检查它是蓝色还是金色;我们预测爱丽丝应该接受与该患者相同的诊断。

    换句话说,为了将 Alice 划分为 CKD 与否,我们在训练集中找到与 Alice “最近”的患者,然后将该患者的诊断用作对 Alice 的预测。直觉上,如果散点图中的两个点彼此靠近,那么相应的测量结果非常相似,所以我们可能会预计,他们(更可能)得到相同的诊断。我们不知道 Alice 的诊断,但是我们知道训练集中所有病人的诊断,所以我们在训练集中找到与 Alice 最相似的病人,并利用病人的诊断来预测 Alice 的诊断。

    在下图中,红点代表爱丽丝。它与距离它最近的点由一条黑线相连,即训练集中最近邻。该图由一个名为show_closest的函数绘制。它需要一个数组,代表 Alice 点的x和y`坐标。改变它们来查看最近的点如何改变!特别注意最近的点是蓝色,以及金色的时候。

    # In this example, Alice's Hemoglobin attribute is 0 and her Glucose is 1.5.
    alice = make_array(0, 1.5)
    show_closest(alice)

    因此,我们的最近邻分类器是这样工作的:

    • 找到训练集中离新点最近的点。
    • 如果最近的点是“CKD”点,则将新点划分为“CKD”。如果最近的点是“非 CKD”点,则将新点划分为“非 CKD”。

    散点图表明这个最近邻分类器应该相当准确。右下角的点倾向于接受“非 CKD”的诊断,因为他们的最近邻是一个金点。其余的点倾向于接受“CKD”诊断,因为他们的最近邻是蓝点。所以这个例子中,最近邻策略似乎很好地捕捉了我们的直觉。

    决策边界

    有时一种分类器可视化的实用方法是,绘制出分类器预测“CKD”的几种属性,以及预测“非 CKD”的几种。我们最终得到两者之间的边界,边界一侧的点将被划分为“CKD”,而另一侧的点将划分为“非 CKD”。这个边界称为决策边界。每个不同的分类器将有不同的决策边界;决策边界只是一种方法,用于可视化分类器实用什么标准来对点分类。

    例如,假设爱丽丝的点坐标是(0, 1.5)。注意最近邻是蓝色的。现在尝试减少点的高度(y坐标)。你会看到,在y = 0.95左右,最近邻从蓝色变为金色。

    alice = make_array(0, 0.97)
    show_closest(alice)

    这里有数百个未分类的新点,都是红色的。

    每个红点在训练集中都有一个最近邻(与之前的蓝点和金点相同)。对于一些红点,你可以很容易地判断最近邻是蓝色还是金色。对于其他点来说,通过眼睛来做出决定更为棘手。那些是靠近决策边界的点。

    但是计算机可以很容易地确定每个点的最近邻。那么让我们将我们的最近邻分类器应用于每个红点:

    对于每个红点,它必须找到训练集中最近的点;它必须将红点的颜色改变为最近邻的颜色。

    结果图显示哪些点将划分为“CKD”(全部为蓝色),或者“非 CKD”(全部为金色)。

    决策边界是分类器从将红点转换为蓝色变成金色的地方。

    KNN

    然而,两个类别的分类并不总是那么清晰。例如,假设我们不用血红蛋白水平而是看白细胞计数。看看会发生什么:

    ckd.scatter('White Blood Cell Count', 'Glucose', colors='Color')

    如你所见,非 CKD 个体都聚集在左下角。大多数 CKD 患者在该簇的上方或右侧,但不是全部。上图左下角有一些 CKD 患者(分散在金簇中的少数蓝点表示)。这意味着你不能从这两个检测结果确定,某些人是否拥有 CKD。

    如果提供爱丽丝的血糖水平和白细胞计数,我们可以预测她是否患有慢性肾病嘛?是的,我们可以做一个预测,但是我们不应该期望它是 100% 准确的。直觉上,似乎存在预测的自然策略:绘制 Alice 在散点图中的位置;如果她在左下角,则预测她没有 CKD,否则预测她有 CKD。

    这并不完美 - 我们的预测有时是错误的。 (请花点时间思考一下,会把哪些患者弄错?)上面的散点图表明,CKD 患者的葡萄糖和白细胞水平有时与没有 CKD 的患者相同,因此任何分类器都是不可避免地会对他们做出错误的预测。

    我们可以在计算机上自动化吗?那么,最近邻分类器也是一个合理的选择。花点时间思考一下:它的预测与上述直觉策略的预测相比如何?他们什么时候会不同?

    它的预测与我们的直觉策略非常相似,但偶尔会做出不同的预测。特别是,如果爱丽丝的血液检测结果恰好把她放在左下角的一个蓝点附近,那么这个直观的策略就可能预测“非 CKD”,而最近邻的分类器会预测“CKD”。

    最近邻分类器有一个简单的推广,修正了这个异常。它被称为 K 最近邻分类器。为了预测爱丽丝的诊断,我们不仅仅查看靠近她的一个邻居,而是查看靠近她的三个点,并用这三个点中的每一个点的诊断来预测艾丽丝的诊断。特别是,我们将使用这 3 个诊断中的大部分值作为我们对 Alice 诊断的预测。当然,数字 3 没有什么特别之处:我们可以使用 4 或 5 或更多。 (选择一个奇数通常是很方便的,所以我们不需要处理相等)。一般来说,我们选择一个数字k,而我们对 Alice 的预测诊断是基于训练集中最接近爱丽丝的k个点。直观来说,这些是血液测试结果与爱丽丝最相似的k个患者,因此使用他们的诊断来预测爱丽丝的诊断似乎是合理的。

    训练和测试

    我们最近的邻居分类器有多好?要回答这个问题,我们需要知道我们的分类有多正确。如果患者患有慢性肾脏疾病,那么我们的分类器有多可能将其选出来呢?

    如果病人在我们的训练集中,我们可以立即找到。我们已经知道病人位于什么类别,所以我们可以比较我们的预测和病人的真实类别。

    但是分类器的重点在于对未在训练集中的新患者进行预测。我们不知道这些病人位于什么类别,但我们可以根据分类器做出预测。如何知道预测是否正确?

    一种方法是等待患者之后的医学检查,然后检查我们的预测是否与检查结果一致。用这种方法,当我们可以说我们的预测有多准确的时候,它就不再能用于帮助病人了。

    相反,我们将在一些真实类别已知的病人上尝试我们的分类器。然后,我们将计算分类器正确的时间比例。这个比例将作为我们分类器准确预测的所有新患者的比例的估计值。这就是所谓的测试。

    过于乐观的“测试”

    训练集提供了一组非常吸引人的患者,我们在它们上测试我们的分类器,因为我们可以知道训练集中每个患者的分类。

    但是,我们要小心,如果我们走这条道路,前面就会有隐患。一个例子会告诉我们为什么。

    假设我们使用 1 邻近分类器,根据血糖和白细胞计数来预测患者是否患有慢性肾病。

    ckd.scatter('White Blood Cell Count', 'Glucose', colors='Color')

    之前,我们说我们预计得到一些分类错误,因为在左下方有一些蓝色和金色的点。

    但是训练集中的点,也就是已经在散点图上的点呢?我们会把它们误分类吗?

    答案是否。请记住,1 最近邻分类寻找训练集中离被分类点最近的点。那么,如果被分类的点已经在训练集中,那么它在训练集中的最近邻就是它自己!因此它将被划分为自己的颜色,这将是正确的,因为训练集中的每个点都已经被正确着色。

    换句话说,如果我们使用我们的训练集来“测试”我们的 1 邻近分类器,分类器将以 100% 的几率内通过测试。

    任务完成。多好的分类器!

    不,不是。正如我们前面提到的,左下角的一个新点很容易被误分类。 “100% 准确”是一个很好的梦想,而它持续。

    这个例子的教训是不要使用训练集来测试基于它的分类器。

    生成测试集

    在前面的章节中,我们看到可以使用随机抽样来估计符合一定标准的总体中的个体比例。不幸的是,我们刚刚看到训练集不像所有患者总体中的随机样本,在一个重要的方面:我们的分类器正确猜测训练集中的个体,比例高于总体中的个体。

    当我们计算数值参数的置信区间时,我们希望从一个总体中得到许多新的随机样本,但是我们只能访问一个样本。我们通过从我们的样本中自举重采样来解决这个问题。

    我们将使用一个类似的想法来测试我们的分类器。我们将从原始训练集中创建两个样本,将其中一个样本作为我们的训练集,另一个用于测试。

    所以我们将有三组个体:

    • 训练集,我们可以对它进行任何大量的探索来建立我们的分类器
    • 一个单独的测试集,在它上面测试我们的分类器,看看分类的正确比例是多少
    • 个体的底层总体,我们不了解它;我们的希望是我们的分类器对于这些个体也会成功,就像我们的测试集一样。

    如何生成训练和测试集?你猜对了 - 我们会随机选择。

    ckd有 158 个个体。让我们将它们随机的一半用于训练,另一半用于测试。为此,我们将打乱所有行,把前 79 个作为训练集,其余的 79 个用于测试。

    shuffled_ckd = ckd.sample(with_replacement=False)
    training = shuffled_ckd.take(np.arange(79))
    testing = shuffled_ckd.take(np.arange(79, 158))

    现在让我们基于训练样本中的点构造我们的分类器:

    training.scatter('White Blood Cell Count', 'Glucose', colors='Color')
    plt.xlim(-2, 6)
    plt.ylim(-2, 6);

    我们得到以下分类区域和决策边界:

    把测试数据放在这个图上,你可以立刻看到分类器对于几乎所有的点都正确,但也有一些错误。 例如,测试集的一些蓝点落在分类器的金色区域。

    尽管存在一些错误,但分类器看起来在测试集上表现得相当好。 假设原始样本是从底层总体中随机抽取的,我们希望分类器在整个总体上具有相似的准确性,因为测试集是从原始样本中随机选取的。

    表的行

    现在我们对最近邻分类有一个定性的了解,是时候实现我们的分类器了。

    在本章之前,我们主要处理表格的单列。 但现在我们必须看看一个个体是否“接近”另一个个体。 个体数据包含在表格的行中。

    那么让我们首先仔细看一下行。

    这里是原始表格ckd,包含慢性肾病患者资料。

    ckd = Table.read_table('ckd.csv').relabeled('Blood Glucose Random', 'Glucose')

    对应第一个患者的数据在表中第 0 行,与 Python 的索引系统一致。 Tablerow方法将行索引作为其参数来访问行。

    ckd.row(0)
    Row(Age=48, Blood Pressure=70, Specific Gravity=1.0049999999999999, Albumin=4, Sugar=0, Red Blood Cells='normal', Pus Cell='abnormal', Pus Cell clumps='present', Bacteria='notpresent', Glucose=117, Blood Urea=56, Serum Creatinine=3.7999999999999998, Sodium=111, Potassium=2.5, Hemoglobin=11.199999999999999, Packed Cell Volume=32, White Blood Cell Count=6700, Red Blood Cell Count=3.8999999999999999, Hypertension='yes', Diabetes Mellitus='no', Coronary Artery Disease='no', Appetite='poor', Pedal Edema='yes', Anemia='yes', Class=1)
    

    行拥有自己的数据类型:它们是行对象。 注意屏幕不仅显示行中的值,还显示相应列的标签。

    行通常不是数组,因为它们的元素可以是不同的类型。 例如,上面那行的一些元素是字符串(如'abnormal'),有些是数字。 所以行不能被转换成数组。

    但是,行与数组有一些特征。 你可以使用item来访问行中的特定元素。 例如,要访问患者 0 的白蛋白水平,我们可以查看上面那行的打印输出中的标签,发现它是第 3 项:

    ckd.row(0).item(3)
    4

    将行转换为数组(可能的时候)

    元素都是数字(或都是字符串)的行可以转换为数组。 将行转换为数组可以让我们访问算术运算和其他漂亮的 NumPy 函数,所以它通常很有用。

    回想一下,在上一节中,我们试图根据血红蛋白和血糖两个属性将患者划分为“CKD”或“非 CKD”,这两个属性都是以标准单位测量的。

    ckd = Table().with_columns(
        'Hemoglobin', standard_units(ckd.column('Hemoglobin')),
        'Glucose', standard_units(ckd.column('Glucose')),
        'Class', ckd.column('Class')
    )
    
    color_table = Table().with_columns(
        'Class', make_array(1, 0),
        'Color', make_array('darkblue', 'gold')
    )
    ckd = ckd.join('Class', color_table)
    ckd
    ClassHemoglobinGlucoseColor
    00.4568840.133751gold
    01.153-0.947597gold
    00.770138-0.762223gold
    00.596108-0.190654gold
    0-0.239236-0.49961gold
    0-0.0304002-0.159758gold
    00.282854-0.00527964gold
    00.108824-0.623193gold
    00.0740178-0.515058gold
    00.83975-0.422371gold

    (省略了 148 行)

    下面是两个属性的散点图,以及新患者 Alice 对应的红点。 她的血红蛋白值是 0(即平均值)和血糖为 1.1(即比平均值高 1.1 个 SD)。

    alice = make_array(0, 1.1)
    ckd.scatter('Hemoglobin', 'Glucose', colors='Color')
    plots.scatter(alice.item(0), alice.item(1), color='red', s=30);

    为了找到 Alice 点和其他点之间的距离,我们只需要属性的值:

    ckd_attributes = ckd.select('Hemoglobin', 'Glucose')
    ckd_attributes
    HemoglobinGlucose
    0.4568840.133751
    1.153-0.947597
    0.770138-0.762223
    0.596108-0.190654
    -0.239236-0.49961
    -0.0304002-0.159758
    0.282854-0.00527964
    0.108824-0.623193
    0.0740178-0.515058
    0.83975-0.422371

    (省略了 148 行)

    每行由我们的训练样本中的一个点的坐标组成。 由于行现在只包含数值,因此可以将它们转换为数组。 为此,我们使用函数np.array,将任何类型的有序对象(如行)转换为数组。 (我们的老朋友make_array用于创建数组,而不是用于将其他类型的序列转换为数组。)

    ckd_attributes.row(3)
    Row(Hemoglobin=0.59610766482326683, Glucose=-0.19065363034327712)
    np.array(ckd_attributes.row(3))
    array([ 0.59610766, -0.19065363])

    这非常方便,因为我们现在可以在每行的数据上使用数组操作了。

    只有两个属性时点的距离

    我们需要做的主要计算是,找出 Alice 的点与其他点之间的距离。 为此,我们需要的第一件事就是计算任意一对点之间的距离。

    我们如何实现呢? 在二维空间中,这非常简单。 如果我们在坐标(x0, y0)处有一个点,而在(x1, y1)处有另一个点,则它们之间的距离是:

    (这是从哪里来的?它来自勾股定理:我们有一个直角三角形,边长为x0 - x1y0 - y1,我们想要求出斜边的长度。)

    在下一节中,我们将看到,当存在两个以上的属性时,这个公式有个直接的扩展。 现在,让我们使用公式和数组操作来求出 Alice 和第 3 行病人的距离。

    patient3 = np.array(ckd_attributes.row(3))
    alice, patient3
    (array([ 0. ,  1.1]), array([ 0.59610766, -0.19065363]))
    distance = np.sqrt(np.sum((alice - patient3)**2))
    distance
    1.4216649188818471

    我们需要 Alice 和一堆点之间的距离,所以让我们写一个称为距离的函数来计算任意一对点之间的距离。 该函数将接受两个数组,每个数组包含一个点的(x, y)坐标。 (记住,那些实际上是患者的血红蛋白和血糖水平。)

    def distance(point1, point2):
        """Returns the Euclidean distance between point1 and point2.
    
        Each argument is an array containing the coordinates of a point."""
        return np.sqrt(np.sum((point1 - point2)**2))
    distance(alice, patient3)
    1.4216649188818471

    我们已经开始建立我们的分类器:距离函数是第一个积木。 现在让我们来研究下一个片段。

    在整个行上使用apply

    回想一下,如果要将函数应用于表的列的每个元素,一种方法是调用table_name.apply(function_name, column_label)。 当我们在列的每个元素上调用该函数时,它求值为由函数返回值组成的数组。所以数组的每个条目都基于表的相应行。

    如果使用apply而不指定列标签,则整行将传递给该函数。 让我们在一个非常小的表格上,看看它的工作原理,表格包含训练样本中前五个患者的信息。

    t = ckd_attributes.take(np.arange(5))
    t
    HemoglobinGlucose
    0.4568840.133751
    1.153-0.947597
    0.770138-0.762223
    0.596108-0.190654
    -0.239236-0.49961

    举个例子,假设对于每个病人,我们都想知道他们最不寻常的属性是多么的不寻常。 具体而言,如果患者的血红蛋白水平远高于其血糖水平,我们想知道它离平均值有多远。 如果她的血糖水平远远高于她的血红蛋白水平,那么我们想知道它离平均值有多远。

    这与获取两个量的绝对值的最大值是一样的。 为了为特定的行执行此操作,我们可以将行转换为数组并使用数组操作。

    def max_abs(row):
        return np.max(np.abs(np.array(row)))
    max_abs(t.row(4))
    0.49961028259186968

    现在我们可以将max_abs应用于t表的每一行:

    t.apply(max_abs)
    array([ 0.4568837 ,  1.15300352,  0.77013762,  0.59610766,  0.49961028])

    这种使用apply的方式帮助我们创建分类器的下一个积木。

    Alice 的 K 最近邻

    如果我们想使用 K 最近邻分类器来划分 Alice,我们必须确定她的 K 个最近邻。 这个过程中的步骤是什么? 假设k = 5。 然后这些步骤是:

    • 步骤 1:的是 Alice 与训练样本中每个点之间的距离。
    • 步骤 2:按照距离的升序对数据表进行排序。
    • 步骤 3:取得有序表的前 5 行。

    步骤 2 和步骤 3 似乎很简单,只要我们有了距离。 那么我们来关注步骤 1。

    这是爱丽丝:

    alice
    array([ 0. ,  1.1])

    我们需要一个函数,它可以求出 Alice 和另一个点之间的距离,它的坐标包含在一行中。 distance函数返回任意两点之间的距离,他们的坐标位于数组中。 我们可以使用它来定义distance_from_alice,它将一行作为参数,并返回该行与 Alice 之间的距离。

    def distance_from_alice(row):
        """Returns distance between Alice and a row of the attributes table"""
        return distance(alice, np.array(row))
    distance_from_alice(ckd_attributes.row(3))
    1.4216649188818471

    现在我们可以调用apply,将distance_from_alice函数应用于ckd_attributes的每一行,第一步完成了。

    distances = ckd_attributes.apply(distance_from_alice)
    ckd_with_distances = ckd.with_column('Distance from Alice', distances)
    ckd_with_distances
    ClassHemoglobinGlucoseColorDistance from Alice
    00.4568840.133751gold1.06882
    01.153-0.947597gold2.34991
    00.770138-0.762223gold2.01519
    00.596108-0.190654gold1.42166
    0-0.239236-0.49961gold1.6174
    0-0.0304002-0.159758gold1.26012
    00.282854-0.00527964gold1.1409
    00.108824-0.623193gold1.72663
    00.0740178-0.515058gold1.61675
    00.83975-0.422371gold1.73862

    (省略了 148 行)

    对于步骤 2,让我们以距离的升序对表排序:

    sorted_by_distance = ckd_with_distances.sort('Distance from Alice')
    sorted_by_distance
    ClassHemoglobinGlucoseColorDistance from Alice
    10.839751.2151darkblue0.847601
    1-0.9701621.27689darkblue0.986156
    0-0.03040020.0874074gold1.01305
    00.143630.0874074gold1.02273
    1-0.4132662.04928darkblue1.03534
    00.3872720.118303gold1.05532
    00.4568840.133751gold1.06882
    00.1784360.0410639gold1.07386
    00.004405820.025616gold1.07439
    0-0.1696240.025616gold1.08769

    (省略了 148 行)

    步骤 3:前五行对应 Alice 的五个最近邻;你可以将五替换为任意正整数。

    alice_5_nearest_neighbors = sorted_by_distance.take(np.arange(5))
    alice_5_nearest_neighbors
    ClassHemoglobinGlucoseColorDistance from Alice
    10.839751.2151darkblue0.847601
    1-0.9701621.27689darkblue0.986156
    0-0.03040020.0874074gold1.01305
    00.143630.0874074gold1.02273
    1-0.4132662.04928darkblue1.03534

    爱丽丝五个最近邻中有三个是蓝点,两个是金点。 所以 5 邻近的分类器会把爱丽丝划分为蓝色:它可能预测爱丽丝有慢性肾病。

    下面的图片放大了爱丽丝和她五个最近邻。 这两个金点就在红点正下方的圆圈内。 分类器说,爱丽丝更像她身边的三个蓝点。

    我们正在实现我们的 K 最近邻分类器。 在接下来的两节中,我们将把它放在一起并评估其准确性。

    实现分类器

    现在我们准备基于多个属性实现 K 最近邻分类器。 到目前为止,我们只使用了两个属性,以便可视化。 但通常预测将基于许多属性。 这里是一个例子,显示了多个属性可能比两个更好。

    钞票检测

    这次我们来看看,预测钞票(例如 20 美元钞票)是伪造还是合法的。 研究人员根据许多单个钞票的照片,为我们汇集了一套数据集:一些是假冒的,一些是合法的。 他们从每张图片中计算出一些数字,使用这门课中我们无需担心的技术。 所以,对于每一张钞票,我们知道了一些数字,它们从钞票的照片以及它的类别(是否是伪造的)中计算。 让我们把它加载到一个表中,并看一下。

    banknotes = Table.read_table('banknote.csv')
    banknotes
    WaveletVarWaveletSkewWaveletCurtEntropyClass
    3.62168.6661-2.8073-0.446990
    4.54598.1674-2.4586-1.46210
    3.866-2.63831.92420.106450
    3.45669.5228-4.0112-3.59440
    0.32924-4.45524.5718-0.98880
    4.36849.6718-3.9606-3.16250
    3.59123.01290.728880.564210
    2.0922-6.818.4636-0.602160
    3.20325.7588-0.75345-0.612510
    1.53569.1772-2.2718-0.735350

    (省略了 1362 行)

    让我们看看,前两个数值是否告诉了我们,任何钞票是否伪造的事情。这里是散点图:

    color_table = Table().with_columns(
        'Class', make_array(1, 0),
        'Color', make_array('darkblue', 'gold')
    )
    banknotes = banknotes.join('Class', color_table)
    banknotes.scatter('WaveletVar', 'WaveletCurt', colors='Color')

    非常有趣! 这两个测量值看起来对于预测钞票是否伪造有帮助。 然而,在这个例子中,你现在可以看到蓝色的簇和金色的簇之间有一些重叠。 这表示基于这两个数字,很难判断钞票是否合法。 不过,你可以使用 K 最近邻分类器来预测钞票的合法性。

    花点时间想一想:假设我们使用k = 11(是假如)。 图中的哪些部分会得到正确的结果,哪些部分会产生错误? 决定边界是什么样子?

    数据中显示的规律可能非常乱。 例如,如果使用与图像不同的一对测量值,我们可以得到以下结果:

    banknotes.scatter('WaveletSkew', 'Entropy', colors='Color')

    似乎存在规律,但它是非常复杂。 尽管如此, K 最近邻分类器仍然可以使用,并将有效地“发现”规律。 这说明了机器学习有多强大:它可以有效地利用规律,我们不曾预料到它,或者我们打算将其编入计算机。

    多个属性

    到目前为止,我一直假设我们有两个属性,可以用来帮助我们做出预测。如果我们有两个以上呢?例如,如果我们有 3 个属性呢?

    这里有一个很酷的部分:你也可以对这个案例使用同样的想法。你需要做的所有事情,就是绘制一个三维散点图,而不是二维的。你仍然可以使用 K 最近邻分类器,但现在计算 3 维而不是 2 维距离,它还是有用。可以,很酷!

    事实上,2 或 3 没有什么特别之处。如果你有 4 个属性,你可以使用 4 维的 K 最近邻分类器。 5 个属性?在五维空间里工作。没有必要在这里停下来!这一切都适用于任意多的属性。你只需在非常高维的空间中工作。它变得有点奇怪 - 不可能可视化,但没关系。计算机算法推广得很好:你需要的所有事情,就是计算距离的能力,这并不难。真是亦可赛艇!

    ax = plt.figure(figsize=(8,8)).add_subplot(111, projection='3d')
    ax.scatter(banknotes.column('WaveletSkew'), 
               banknotes.column('WaveletVar'), 
               banknotes.column('WaveletCurt'), 
               c=banknotes.column('Color'));

    真棒!只用 2 个属性,两个簇之间有一些重叠(这意味着对于重叠中的一些点,分类器必然犯一些错误)。但是当我们使用这三个属性时,两个簇几乎没有重叠。换句话说,使用这 3 个属性的分类器比仅使用 2 个属性的分类器更精确。

    这是分类中的普遍现象。每个属性都可能会给你提供新的信息,所以更多的属性有时可以帮助你建立一个更好的分类器。当然开销是,现在我们必须收集更多的信息来衡量每个属性的值,但是如果这个开销显着提高了我们的分类器的精度,那么它可能非常值得。

    综上所述:你现在知道如何使用 K 最近邻分类,预测是与否的问题的答案,基于一些属性值,假设你有一个带有样本的训练集,其中正确的预测已知。总的路线图是这样的:

    找出一些属性,你认为可能帮助你预测问题的答案。
    收集一组训练样本,其中你知道属性值以及正确预测。
    为了预测未来,测量属性的值,然后使用 K 最近邻分类来预测问题的答案。

    多维距离

    我们知道如何在二维空间中计算距离。 如果我们在坐标(x0, y0)处有一个点,而在(x1, y1)处有另一个点,则它们之间的距离是:

    在三维空间中,点是(x0, y0, z0)(x1, y1, z1),它们之间的距离公式为:

    在 N 维空间中,东西有点难以可视化,但我想你可以看到公式是如何推广的:我们总结每个独立坐标差的平方,然后取平方根。

    在最后一节中,我们定义了函数distance返回两点之间距离。 我们在二维中使用它,但好消息是函数并不关心有多少维! 它只是将两个坐标数组相减(无论数组有多长),求差值的平方并加起来,然后取平方根。 我们不必更改代码就可以在多个维度上工作。

    def distance(point1, point2):
        """Returns the distance between point1 and point2
        where each argument is an array 
        consisting of the coordinates of the point"""
        return np.sqrt(np.sum((point1 - point2)**2))

    我们在这个新的数据集上使用它。 wine表含有 178 种不同的意大利葡萄酒的化学成分。 这些类别是葡萄品种,称为品种。 有三个类别,但我们只看看是否可以把第一类和其他两个类别分开。

    wine = Table.read_table('wine.csv')
    
    # For converting Class to binary
    
    def is_one(x):
        if x == 1:
            return 1
        else:
            return 0
    
    wine = wine.with_column('Class', wine.apply(is_one, 0))
    wine
    ClassAlcoholMalic AcidAshAlcalinity of AshMagnesiumTotal PhenolsFlavanoidsNonflavanoid phenolsProanthocyaninsColor IntensityHueOD280/OD315 of diulted winesProline
    114.231.712.4315.61272.83.060.282.295.641.043.921065
    113.21.782.1411.21002.652.760.261.284.381.053.41050
    113.162.362.6718.61012.83.240.32.815.681.033.171185
    114.371.952.516.81133.853.490.242.187.80.863.451480
    113.242.592.87211182.82.690.391.824.321.042.93735
    114.21.762.4515.21123.273.390.341.976.751.052.851450
    114.391.872.4514.6962.52.520.31.985.251.023.581290
    114.062.152.6117.61212.62.510.311.255.051.063.581295
    114.831.642.1714972.82.980.291.985.21.082.851045
    113.861.352.2716982.983.150.221.857.221.013.551045

    前两种葡萄酒都属于第一类。为了找到它们之间的距离,我们首先需要一个只有属性的表格:

    wine_attributes = wine.drop('Class')
    distance(np.array(wine_attributes.row(0)), np.array(wine_attributes.row(1)))
    31.265012394048398

    中的最后一个葡萄酒是第零类。它与第一个葡萄酒的距离是:

    distance(np.array(wine_attributes.row(0)), np.array(wine_attributes.row(177)))
    506.05936766351834

    这也太大了! 让我们做一些可视化,看看第一类是否真的看起来不同于第零类。

    wine_with_colors = wine.join('Class', color_table)
    wine_with_colors.scatter('Flavanoids', 'Alcohol', colors='Color')

    蓝点(第一类)几乎完全与金点分离。 这表明了,为什么两种第一类葡萄酒之间的距离小于两个不同类别葡萄酒之间的距离。 我们使用不同的一对属性,也可以看到类似的现象:

    wine_with_colors.scatter('Alcalinity of Ash', 'Ash', colors='Color')

    但是对于不同的偶对,图像更加模糊。

    wine_with_colors.scatter('Magnesium', 'Total Phenols', colors='Color')

    让我们来看看,是否可以基于所有的属性来实现一个分类器。 之后,我们会看到它有多准确。

    实现计划

    现在是时候编写一些代码来实现分类器了。 输入是我们要分类的一个点。 分类器的原理是,找到训练集中的 K 个最近邻点。 所以,我们的方法将会是这样:

    找出最接近的 K 个点,即训练集中与点最相似的 K 个葡萄酒。

    看看这些 K 个邻居的类别,并取大多数,找到最普遍的葡萄酒类别。 用它作为我们对点的预测。

    所以这将指导我们的 Python 代码的结构。

    def closest(training, p, k):
        ...
    
    def majority(topkclasses):
        ...
    
    def classify(training, p, k):
        kclosest = closest(training, p, k)
        kclosest.classes = kclosest.select('Class')
        return majority(kclosest)

    实现步骤 1

    为了为肾病数据实现第一步,我们必须计算点到训练集中每个患者的距离,按照距离排序,并取出训练集中最接近的 K 个患者。

    这就是我们在上一节中使用对应 Alice 的点所做的事情。 我们来概括一下这个代码。 我们将在这里重新定义distance,只是为了方便。

    def distance(point1, point2):
        """Returns the distance between point1 and point2
        where each argument is an array 
        consisting of the coordinates of the point"""
        return np.sqrt(np.sum((point1 - point2)**2))
    
    def all_distances(training, new_point):
        """Returns an array of distances
        between each point in the training set
        and the new point (which is a row of attributes)"""
        attributes = training.drop('Class')
        def distance_from_point(row):
            return distance(np.array(new_point), np.array(row))
        return attributes.apply(distance_from_point)
    
    def table_with_distances(training, new_point):
        """Augments the training table 
        with a column of distances from new_point"""
        return training.with_column('Distance', all_distances(training, new_point))
    
    def closest(training, new_point, k):
        """Returns a table of the k rows of the augmented table
        corresponding to the k smallest distances"""
        with_dists = table_with_distances(training, new_point)
        sorted_by_distance = with_dists.sort('Distance')
        topk = sorted_by_distance.take(np.arange(k))
        return topk

    让我们看看它如何在我们的葡萄酒数据上工作。 我们只要取第一个葡萄酒,在所有葡萄酒中找到最近的五个邻居。 请记住,由于这个葡萄酒是数据集的一部分,因此它自己是最近的邻居。 所以我们应该预计看到,它在列表顶端,后面是其他四个。

    首先让我们来提取它的属性:

    special_wine = wine.drop('Class').row(0)

    现在让我们找到它的五个最近邻:

    closest(wine, special_wine, 5)
    ClassAlcoholMalic AcidAshAlcalinity of AshMagnesiumTotal PhenolsFlavanoidsNonflavanoid phenolsProanthocyaninsColor IntensityHueOD280/OD315 of diulted winesProlineDistance
    114.231.712.4315.61272.83.060.282.295.641.043.9210650
    113.741.672.2516.41182.62.90.211.625.850.923.2106010.3928
    114.214.042.4418.91112.852.650.31.255.240.873.33108022.3407
    114.12.022.418.81032.752.920.322.386.21.072.75106024.7602
    114.383.592.28161023.253.170.272.194.91.043.44106525.0947

    好的! 第一行是最近邻,这是它自己 - Distance中值为零,和预期一样。 所有五个最近邻都属于第一类,这与我们先前的观察结果一致,即第一类葡萄酒集中在某些维度。

    实现步骤 2 和 3

    接下来,我们需要获取最近邻的“最大计数”,并把我们的点分配给大多数的相同类别。

    def majority(topkclasses):
        ones = topkclasses.where('Class', are.equal_to(1)).num_rows
        zeros = topkclasses.where('Class', are.equal_to(0)).num_rows
        if ones > zeros:
            return 1
        else:
            return 0
    
    def classify(training, new_point, k):
        closestk = closest(training, new_point, k)
        topkclasses = closestk.select('Class')
        return majority(topkclasses)
    classify(wine, special_wine, 5)
    1

    如果将special_wine改为数据集中的最后一个,我们的分类器是否能够判断它在第零类中嘛?

    special_wine = wine.drop('Class').row(177)
    classify(wine, special_wine, 5)
    0

    是的! 分类器弄对了。

    但是我们还不知道它对于所有其它葡萄酒如何,而且无论如何我们都知道,测试已经属于训练集的葡萄酒可能过于乐观了。 在本章的最后部分,我们将葡萄酒分为训练集和测试集,然后测量分类器在测试集上的准确性。

    分类器的准确性

    为了看看我们的分类器做得如何,我们可以将 50% 的数据放入训练集,另外 50% 放入测试集。基本上,我们保留一些数据以便以后使用,所以我们可以用它来测量分类器的准确性。我们始终将这个称为测试集。有时候,人们会把你留下用于测试的数据叫做保留集,他们会把这个估计准确率的策略称为保留方法。

    请注意,这种方法需要严格的纪律。在开始使用机器学习方法之前,你必须先取出一些数据,然后放在一边用于测试。你必须避免使用测试集来开发你的分类器:你不应该用它来帮助训练你的分类器或者调整它的设置,或者用头脑风暴的方式来改进你的分类器。相反,在最后你已经完成分类器之后,当你想要它的准确率的无偏估计时,你应该仅仅使用它使用一次。

    测量我们的葡萄酒分类器的准确率

    好吧,让我们应用保留方法来评估 K 最近邻分类器识别葡萄酒的有效性。数据集有 178 个葡萄酒,所以我们将随机排列数据集,并将其中的 89 个放在训练集中,其余 89 个放在测试集中。

    shuffled_wine = wine.sample(with_replacement=False) 
    training_set = shuffled_wine.take(np.arange(89))
    test_set  = shuffled_wine.take(np.arange(89, 178))

    我们将使用训练集中的 89 个葡萄酒来训练分类器,并评估其在测试集上的表现。 为了让我们更轻松,我们将编写一个函数,在测试集中每个葡萄酒上评估分类器:

    def count_zero(array):
        """Counts the number of 0's in an array"""
        return len(array) - np.count_nonzero(array)
    
    def count_equal(array1, array2):
        """Takes two numerical arrays of equal length
        and counts the indices where the two are equal"""
        return count_zero(array1 - array2)
    
    def evaluate_accuracy(training, test, k):
        test_attributes = test.drop('Class')
        def classify_testrow(row):
            return classify(training, row, k)
        c = test_attributes.apply(classify_testrow)
        return count_equal(c, test.column('Class')) / test.num_rows

    现在到了答案揭晓的时候了,我们来看看我们做得如何。 我们将任意使用k = 5

    evaluate_accuracy(training_set, test_set, 5)
    0.9213483146067416

    对于一个简单的分类器来说,这个准确率完全不差。

    乳腺癌诊断

    现在我想展示乳腺癌诊断的例子。我受到布列塔尼·温格(Brittany Wenger)的启发,他在 2012 年赢得了谷歌科学竞赛,还是一位 17 岁的高中生。这是布列塔尼:

    布列塔尼的科学竞赛项目是构建一个诊断乳腺癌的分类算法。由于她构建了一个精度接近 99% 的算法,她获得了大奖。

    让我们看看我们能做得如何,使用我们在这个课程中学到的思路。

    所以,让我告诉你一些数据集的信息。基本上,如果一个女性的乳房存在肿块,医生可能想要进行活检,看看它是否是癌症。有几个不同的过程用于实现它。布列塔尼专注于细针抽吸(FNA),因为它比替代方案的侵袭性小。医生得到一块样本,放在显微镜下,拍摄一张照片,一个训练有素的实验室技术人员分析图像,来确定是否是癌症。我们得到一张图片,像下面这样:

    不幸的是,区分良性和恶性可能是棘手的。因此,研究人员已经研究了机器学习的用法,来帮助完成这项任务。我们的想法是,我们要求实验室技术人员分析图像并计算各种属性:诸如细胞的通常大小,细胞大小之间有多少变化等等。然后,我们将尝试使用这些信息来预测(分类)样本是否是恶性的。我们有一套来自女性的过去样本的训练集,其中正确的诊断已知,我们希望我们的机器学习算法可以使用它们来学习如何预测未来样本的诊断。

    我们最后得到了以下数据集。对于Class列,1 表示恶性(癌症);0 意味着良性(不是癌症)。

    patients = Table.read_table('breast-cancer.csv').drop('ID')
    patients
    Clump ThicknessUniformity of Cell SizeUniformity of Cell ShapeMarginal AdhesionSingle Epithelial Cell SizeBare NucleiBland ChromatinNormal NucleoliMitosesClass
    5111213110
    54457103210
    3111223110
    6881343710
    4113213110
    8101087109711
    11112103110
    2121213110
    2111211150
    4211212110

    (省略了 673 行)

    所以我们有 9 个不同的属性。 我不知道如何制作它们全部的 9 维散点图,所以我要挑选两个并绘制它们:

    color_table = Table().with_columns(
        'Class', make_array(1, 0),
        'Color', make_array('darkblue', 'gold')
    )
    patients_with_colors = patients.join('Class', color_table)
    patients_with_colors.scatter('Bland Chromatin', 'Single Epithelial Cell Size', colors='Color')

    这个绘图完全是误导性的,因为有一堆点的x坐标和y坐标都有相同的值。 为了更容易看到所有的数据点,我将为xy值添加一点点随机抖动。 这是看起来的样子:

    例如,你可以看到有大量的染色质为 2 和上皮细胞大小为 2 的样本;所有都不是癌症。

    请记住,抖动仅用于可视化目的,为了更容易感知数据。 我们现在已经准备好使用这些数据了,我们将使用原始数据(没有抖动)。

    首先,我们将创建一个训练集和一个测试集。 数据集有 683 名患者,因此我们将随机排列数据集,并将其中的 342 个放在训练集中,其余的 341 个放在测试集中。

    shuffled_patients = patients.sample(683, with_replacement=False) 
    training_set = shuffled_patients.take(np.arange(342))
    test_set  = shuffled_patients.take(np.arange(342, 683))

    让我们选取 5 个最近邻,并观察我们的分类器如何。

    evaluate_accuracy(training_set, test_set, 5)
    0.967741935483871

    准确性超过 96%。不错!这样一个简单的技术再一次相当不错。

    作为脚注,你可能已经注意到布列塔尼·温格做得更好了。 她使用了什么技术? 一个关键的创新是,她将置信评分纳入了结果:她的算法有一种方法来确定何时无法做出有把握的预测,对于那些患者,甚至不尝试预测他们的诊断。 她的算法对于做出预测的病人是 99% 准确的,所以这个扩展看起来有点帮助。

    多元回归

    现在我们已经探索了使用多个属性来预测类别变量的方法,让我们返回来预测定量变量。 预测数值量被称为回归,多个属性进行回归的常用方法称为多元线性回归。

    房价

    下面的房价和属性数据集在爱荷华州埃姆斯市收集了数年。 数据集的描述在线显示。 我们将仅仅关注列的一个子集。 我们将尝试从其它列中预测价格列。

    all_sales = Table.read_table('house.csv')
    sales = all_sales.where('Bldg Type', '1Fam').where('Sale Condition', 'Normal').select(
        'SalePrice', '1st Flr SF', '2nd Flr SF', 
        'Total Bsmt SF', 'Garage Area', 
        'Wood Deck SF', 'Open Porch SF', 'Lot Area', 
        'Year Built', 'Yr Sold')
    sales.sort('SalePrice')
    SalePrice1st Flr SF2nd Flr SFTotal Bsmt SFGarage AreaWood Deck SFOpen Porch SFLot AreaYear BuiltYr Sold
    35000498049821600808819222006
    3930033400000500019462007
    40000649668649250054850019202008
    450006120030800592519402009
    520007290270000413019352008
    5250069306930020411819412006
    550007233637234000241134019202008
    550007960796000363619222008
    5762581000280119242178019102009
    58500864086420000821219142010

    (省略了 1992 行)

    销售价格的直方图显示出大量的变化,分布显然不是正态。 右边的长尾包含几个价格非常高的房屋。 左边的短尾不包含任何售价低于 35,000 美元的房屋。

    sales.hist('SalePrice', bins=32, unit='$')

    相关性

    没有单个属性足以预测销售价格。 例如,第一层面积(平方英尺)与销售价格相关,但仅解释其一些变化。

    sales.scatter('1st Flr SF', 'SalePrice')
    
    correlation(sales, 'SalePrice', '1st Flr SF')
    0.64246625410302249

    事实上,没有任何单个属性与销售价格的相关性大于 0.7(销售价格本身除外)。

    for label in sales.labels:
        print('Correlation of', label, 'and SalePrice:\t', correlation(sales, label, 'SalePrice'))
    Correlation of SalePrice and SalePrice:     1.0
    Correlation of 1st Flr SF and SalePrice:     0.642466254103
    Correlation of 2nd Flr SF and SalePrice:     0.35752189428
    Correlation of Total Bsmt SF and SalePrice:     0.652978626757
    Correlation of Garage Area and SalePrice:     0.638594485252
    Correlation of Wood Deck SF and SalePrice:     0.352698666195
    Correlation of Open Porch SF and SalePrice:     0.336909417026
    Correlation of Lot Area and SalePrice:     0.290823455116
    Correlation of Year Built and SalePrice:     0.565164753714
    Correlation of Yr Sold and SalePrice:     0.0259485790807

    但是,组合属性可以提供更高的相关性。 特别是,如果我们总结一楼和二楼的面积,那么结果的相关性就比任何单独的属性都要高。

    both_floors = sales.column(1) + sales.column(2)
    correlation(sales.with_column('Both Floors', both_floors), 'SalePrice', 'Both Floors')
    0.7821920556134877

    这种高度相关性表明,我们应该尝试使用多个属性来预测销售价格。 在具有多个观测属性和要预测的单个数值(这里是销售价格)的数据集中,多重线性回归可能是有效的技术。

    多元线性回归

    在多元线性回归中,通过将每个属性值乘以不同的斜率,从数值输入属性预测数值输出,然后对结果求和。 在这个例子中,第一层的斜率将代表房屋第一层面积的美元每平方英尺,它应该用于我们的预测。

    在开始预测之前,我们将数据随机分成一个相同大小的训练和测试集。

    train, test = sales.split(1001)
    print(train.num_rows, 'training and', test.num_rows, 'test instances.')
    1001 training and 1001 test instances.

    多元回归中的斜率是一个数组,例子中每个属性拥有一个斜率值。 预测销售价格包括,将每个属性乘以斜率并将结果相加。

    def predict(slopes, row):
        return sum(slopes * np.array(row))
    
    example_row = test.drop('SalePrice').row(0)
    print('Predicting sale price for:', example_row)
    example_slopes = np.random.normal(10, 1, len(example_row))
    print('Using slopes:', example_slopes)
    print('Result:', predict(example_slopes, example_row))
    Predicting sale price for: Row(1st Flr SF=1092, 2nd Flr SF=1020, Total Bsmt SF=952.0, Garage Area=576.0, Wood Deck SF=280, Open Porch SF=0, Lot Area=11075, Year Built=1969, Yr Sold=2008)
    Using slopes: [  9.99777721   9.019661    11.13178317   9.40645585  11.07998556
      11.03830075  10.26908341  10.42534332  11.00103437]
    Result: 195583.275784

    结果是估计的销售价格,可以将其与实际销售价格进行比较,以评估斜率是否提供准确的预测。 由于上面的example_slopes是随机选取的,我们不应该期望它们提供准确的预测。

    print('Actual sale price:', test.column('SalePrice').item(0))
    print('Predicted sale price using random slopes:', predict(example_slopes, example_row))
    Actual sale price: 206900
    Predicted sale price using random slopes: 195583.275784

    最小二乘回归

    执行多元回归的下一步是定义最小二乘目标。 我们对训练集中的每一行执行预测,然后根据实际价格计算预测的均方根误差(RMSE)。

    train_prices = train.column(0)
    train_attributes = train.drop(0)
    
    def rmse(slopes, attributes, prices):
        errors = []
        for i in np.arange(len(prices)):
            predicted = predict(slopes, attributes.row(i))
            actual = prices.item(i)
            errors.append((predicted - actual) ** 2)
        return np.mean(errors) ** 0.5
    
    def rmse_train(slopes):
        return rmse(slopes, train_attributes, train_prices)
    
    print('RMSE of all training examples using random slopes:', rmse_train(example_slopes))
    RMSE of all training examples using random slopes: 69653.9880638

    最后,我们使用minimize函数来找到使 RMSE 最低的斜率。 由于我们想要最小化的函数rmse_train需要一个数组而不是一个数字,所以我们必须向minimize函数传递array = True参数。 当使用这个参数时,minimize也需要斜率的初始猜测,以便知道输入数组的维数。 最后,为了加速优化,我们使用smooth = True属性,指出rmse_train是一个平滑函数。 计算最佳斜率可能需要几分钟的时间。

    best_slopes = minimize(rmse_train, start=example_slopes, smooth=True, array=True)
    print('The best slopes for the training set:')
    Table(train_attributes.labels).with_row(list(best_slopes)).show()
    print('RMSE of all training examples using the best slopes:', rmse_train(best_slopes))
    The best slopes for the training set:
    1st Flr SF2nd Flr SFTotal Bsmt SFGarage AreaWood Deck SFOpen Porch SFLot AreaYear BuiltYr Sold
    73.777972.305751.888546.558139.326711.9960.451265538.243-534.634
    RMSE of all training examples using the best slopes: 31146.4442711

    解释多元线性回归

    让我们来解释这些结果。 最佳斜率为我们提供了一个方法,从其房屋属性估算价格。 一楼的面积约为 75 美元每平方英尺(第一个斜率),而二楼的面积约为 70 元每平方英尺(第二个斜率)。 最后的负值描述了市场:最近几年的价格平均较低。

    大约 3 万美元的 RMSE 意味着,我们基于所有属性的销售价格的最佳线性预测,在训练集上平均差了大约 3 万美元。 当预测测试集的价格时,我们发现了类似的误差,这表明我们的预测方法可推广到来自同一总体的其他样本。

    test_prices = test.column(0)
    test_attributes = test.drop(0)
    
    def rmse_test(slopes):
        return rmse(slopes, test_attributes, test_prices)
    
    rmse_linear = rmse_test(best_slopes)
    print('Test set RMSE for multiple linear regression:', rmse_linear)
    Test set RMSE for multiple linear regression: 31105.4799398

    如果预测是完美的,那么预测值和实际值的散点图将是一条斜率为 1 的直线。我们可以看到大多数点落在该线附近,但预测中存在一些误差。

    def fit(row):
        return sum(best_slopes * np.array(row))
    
    test.with_column('Fitted', test.drop(0).apply(fit)).scatter('Fitted', 0)
    plots.plot([0, 5e5], [0, 5e5]);

    多元回归的残差图通常将误差(残差)与预测变量的实际值进行比较。 我们在下面的残差图中看到,我们系统性低估了昂贵房屋的值,由图右侧的许多正的残差值所示。

    test.with_column('Residual', test_prices-test.drop(0).apply(fit)).scatter(0, 'Residual')
    plots.plot([0, 7e5], [0, 0]);

    就像简单的线性回归一样,解释预测结果至少和预测一样重要。 很多解释多元回归的课程不包含在这个课本中。 完成这门课之后的下一步自然是深入研究线性建模和回归。

    最近邻回归

    另一种预测房屋销售价格的方法是使用类似房屋的价格。 这个最近邻的方法与我们的分类器非常相似。 为了加速计算,我们将只使用与原始分析中销售价格相关性最高的属性。

    train_nn = train.select(0, 1, 2, 3, 4, 8)
    test_nn = test.select(0, 1, 2, 3, 4, 8)
    train_nn.show(3)
    SalePrice1st Flr SF2nd Flr SFTotal Bsmt SFGarage AreaYear Built
    2400001710017105502004
    22900013027356724721996
    13650086408643361978

    (省略了 998 行)

    最近邻的计算与最近邻分类器相同。 在这种情况下,我们将从距离计算中排除'SalePrice'而不是'Class'列。 第一个测试行的五个最近邻如下所示。

    def distance(pt1, pt2):
        """The distance between two points, represented as arrays."""
        return np.sqrt(sum((pt1 - pt2) ** 2))
    
    def row_distance(row1, row2):
        """The distance between two rows of a table."""
        return distance(np.array(row1), np.array(row2))
    
    def distances(training, example, output):
        """Compute the distance from example for each row in training."""
        dists = []
        attributes = training.drop(output)
        for row in attributes.rows:
            dists.append(row_distance(row, example))
        return training.with_column('Distance', dists)
    
    def closest(training, example, k, output):
        """Return a table of the k closest neighbors to example."""
        return distances(training, example, output).sort('Distance').take(np.arange(k))
    
    example_nn_row = test_nn.drop(0).row(0)
    closest(train_nn, example_nn_row, 5, 'SalePrice')
    SalePrice1st Flr SF2nd Flr SFTotal Bsmt SFGarage AreaYear BuiltDistance
    15000012990967494195451.9711
    144000134401024484195860.8358
    183500129901001486197968.6003
    14000012830931506196276.5049
    17300012870957541197777.2464

    预测价格的一个简单方法是计算最近邻的价格均值。

    def predict_nn(example):
        """Return the majority class among the k nearest neighbors."""
        return np.average(closest(train_nn, example, 5, 'SalePrice').column('SalePrice'))
    
    predict_nn(example_nn_row)
    158100.0

    最后,我们可以使用一个测试样本,检查我们的预测是否接近真实销售价格。 看起来很合理!

    print('Actual sale price:', test_nn.column('SalePrice').item(0))
    print('Predicted sale price using nearest neighbors:', predict_nn(example_nn_row))
    Actual sale price: 146000
    Predicted sale price using nearest neighbors: 158100.0

    尾注

    为了为整个测试集评估这个方法的性能,我们将predict_nn应用于每个测试示例,然后计算预测的均方根误差。 预测的计算可能需要几分钟的时间。

    nn_test_predictions = test_nn.drop('SalePrice').apply(predict_nn)
    rmse_nn = np.mean((test_prices - nn_test_predictions) ** 2) ** 0.5
    
    print('Test set RMSE for multiple linear regression: ', rmse_linear)
    print('Test set RMSE for nearest neighbor regression:', rmse_nn)
    Test set RMSE for multiple linear regression:  30232.0744208
    Test set RMSE for nearest neighbor regression: 31210.6572877

    对于这些数据,这两种技术的误差非常相似! 对于不同的数据集,一种技术可能会胜过另一种。 通过计算两种技术在同一数据上的均方根误差,我们可以公平比较这些方法。值得注意的是:表现的差异可能不完全由于技术;这可能由于随机变化,由于首先对训练和测试集进行抽样。

    最后,我们可以为这些预测画出一个残差图。 我们仍然低估了最昂贵房屋的价格,但偏差似乎并不像系统性的。 然而,较低价格的残差非常接近零,这表明较低价格的预测准确性非常高。

    test.with_column('Residual', test_prices-nn_test_predictions).scatter(0, 'Residual')
    plots.plot([0, 7e5], [0, 0]);

    展开全文
  • 第十章 爬取维基百科 原文:Chapter 15 Crawling Wikipedia 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 在本章中,我展示了上一个练习的解决方案,并分析了 Web 索引算法的性能。然后我们...

    第十五章 爬取维基百科

    原文:Chapter 15 Crawling Wikipedia

    译者:飞龙

    协议:CC BY-NC-SA 4.0

    自豪地采用谷歌翻译

    在本章中,我展示了上一个练习的解决方案,并分析了 Web 索引算法的性能。然后我们构建一个简单的 Web 爬虫。

    15.1 基于 Redis 的索引器

    在我的解决方案中,我们在 Redis 中存储两种结构:

    • 对于每个检索词,我们有一个URLSet,它是一个 Redis 集合,包含检索词的 URL。
    • 对于每个网址,我们有一个TermCounter,这是一个 Redis 哈希表,将每个检索词映射到它出现的次数。

    我们在上一章讨论了这些数据类型。你还可以在 http://thinkdast.com/redistypes 上阅读 Redis Set和Hash`的信息

    JedisIndex中,我提供了一个方法,它可以接受一个检索词并返回 Redis 中它的URLSet的键:

    private String urlSetKey(String term) {
        return "URLSet:" + term;
    }

    以及一个方法,接受 URL 并返回 Redis 中它的TermCounter的键。

    private String termCounterKey(String url) {
        return "TermCounter:" + url;
    }

    这里是indexPage的实现。

    public void indexPage(String url, Elements paragraphs) {
        System.out.println("Indexing " + url);
    
        // make a TermCounter and count the terms in the paragraphs
        TermCounter tc = new TermCounter(url);
        tc.processElements(paragraphs);
    
        // push the contents of the TermCounter to Redis
        pushTermCounterToRedis(tc);
    }

    为了索引页面,我们:

    • 为页面内容创建一个 Java 的TermCounter,使用上一个练习中的代码。
    • TermCounter的内容推送到 Redis。

    以下是将TermCounter的内容推送到 Redis 的新代码:

    public List<Object> pushTermCounterToRedis(TermCounter tc) {
        Transaction t = jedis.multi();
    
        String url = tc.getLabel();
        String hashname = termCounterKey(url);
    
        // if this page has already been indexed, delete the old hash
        t.del(hashname);
    
        // for each term, add an entry in the TermCounter and a new
        // member of the index
        for (String term: tc.keySet()) {
            Integer count = tc.get(term);
            t.hset(hashname, term, count.toString());
            t.sadd(urlSetKey(term), url);
        }
        List<Object> res = t.exec();
        return res;
    }

    该方法使用Transaction来收集操作,并将它们一次性发送到服务器,这比发送一系列较小操作要快得多。

    它遍历TermCounter中的检索词。对于每一个,它:

    • 在 Redis 上寻找或者创建TermCounter,然后为新的检索词添加字段。
    • 在 Redis 上寻找或创建URLSet,然后添加当前的 URL。

    如果页面已被索引,则TermCounter在推送新内容之前删除旧页面 。

    新的页面的索引就是这样。

    练习的第二部分要求你编写getCounts,它需要一个检索词,并从该词出现的每个网址返回一个映射。这是我的解决方案:

        public Map<String, Integer> getCounts(String term) {
            Map<String, Integer> map = new HashMap<String, Integer>();
            Set<String> urls = getURLs(term);
            for (String url: urls) {
                Integer count = getCount(url, term);
                map.put(url, count);
            }
            return map;
        }

    此方法使用两种辅助方法:

    • getURLs接受检索词并返回该字词出现的网址集合。
    • getCount接受 URL 和检索词,并返回该术语在给定 URL 处显示的次数。

    以下是实现:

        public Set<String> getURLs(String term) {
            Set<String> set = jedis.smembers(urlSetKey(term));
            return set;
        }
    
        public Integer getCount(String url, String term) {
            String redisKey = termCounterKey(url);
            String count = jedis.hget(redisKey, term);
            return new Integer(count);
        }

    由于我们设计索引的方式,这些方法简单而高效。

    15.2 查找的分析

    假设我们索引了N个页面,并发现了M个唯一的检索词。检索词的查询需要多长时间?在继续之前,先考虑一下你的答案。

    要查找一个检索词,我们调用getCounts,其中:

    • 创建映射。
    • 调用getURLs来获取 URL 的集合。
    • 对于集合中的每个 URL,调用getCount并将条目添加到HashMap

    getURLs所需时间与包含检索词的网址数成正比。对于罕见的检索词,这可能是一个很小的数字,但是对于常见检索词,它可能和N一样大。

    在循环中,我们调用了getCount,它在 Redis 上寻找TermCounter,查找一个检索词,并向HashMap添加一个条目。那些都是常数时间的操作,所以在最坏的情况下,getCounts的整体复杂度是O(N)。然而实际上,运行时间正比于包含检索词的页面数量,通常比N小得多。

    这个算法根据复杂性是有效的,但是它非常慢,因为它向 Redis 发送了许多较小的操作。你可以使用Transaction来加快速度 。你可能留作一个练习,或者你可以在RedisIndex.java中查看我的解决方案。

    15.3 索引的分析

    使用我们设计的数据结构,页面的索引需要多长时间?再次考虑你的答案,然后再继续。

    为了索引页面,我们遍历其 DOM 树,找到所有TextNode对象,并将字符串拆分成检索词。这一切都与页面上的单词数成正比。

    对于每个检索词,我们在HashMap中增加一个计数器,这是一个常数时间的操作。所以创建TermCounter的所需时间与页面上的单词数成正比。

    TermCounter推送到 Redis ,需要删除TermCounter,对于唯一检索词的数量是线性的。那么对于每个检索词,我们必须:

    • URLSet添加元素,并且
    • 向 RedisTermCounter添加元素。

    这两个都是常数时间的操作,所以推送TermCounter的总时间对于唯一检索词的数量是线性的。

    总之,TermCounter的创建与页面上的单词数成正比。向 Redis 推送TermCounter与唯一检索词的数量成正比。

    由于页面上的单词数量通常超过唯一检索词的数量,因此整体复杂度与页面上的单词数成正比。理论上,一个页面可能包含索引中的所有检索词,因此最坏的情况是O(M),但实际上我们并不期待看到更糟糕的情况。

    这个分析提出了一种提高效率的方法:我们应该避免索引很常见的词语。首先,他们占用了大量的时间和空间,因为它们出现在几乎每一个URLSetTermCounter中。此外,它们不是很有用,因为它们不能帮助识别相关页面。

    大多数搜索引擎避免索引常用单词,这在本文中称为停止词(http://thinkdast.com/stopword)。

    15.4 图的遍历

    如果你在第七章中完成了“到达哲学”练习,你已经有了一个程序,它读取维基百科页面,找到第一个链接,使用链接加载下一页,然后重复。这个程序是一种专用的爬虫,但是当人们说“网络爬虫”时,他们通常意味着一个程序:

    加载起始页面并对内容进行索引,
    查找页面上的所有链接,并将链接的 URL 添加到集合中
    通过收集,加载和索引页面,以及添加新的 URL,来按照它的方式工作。
    如果它找到已经被索引的 URL,会跳过它。

    你可以将 Web 视为图,其中每个页面都是一个节点,每个链接都是从一个节点到另一个节点的有向边。如果你不熟悉图,可以阅读 http://thinkdast.com/graph

    从源节点开始,爬虫程序遍历该图,访问每个可达节点一次。

    我们用于存储 URL 的集合决定了爬虫程序执行哪种遍历:

    • 如果它是先进先出(FIFO)的队列,则爬虫程序将执行广度优先遍历。
    • 如果它是后进先出(LIFO)的栈,则爬虫程序将执行深度优先遍历。
    • 更通常来说,集合中的条目可能具有优先级。例如,我们可能希望对尚未编入索引的页面给予较高的优先级。

    你可以在 http://thinkdast.com/graphtrav 上阅读图的遍历的更多信息 。

    15.5 练习 12

    现在是时候写爬虫了。在本书的仓库中,你将找到此练习的源文件:

    • WikiCrawler.java,包含你的爬虫的其实代码。
    • WikiCrawlerTest.java,包含WikiCrawler的测试代码。
    • JedisIndex.java,这是我以前的练习的解决方案。

    你还需要一些我们以前练习中使用过的辅助类:

    • JedisMaker.java
    • WikiFetcher.java
    • TermCounter.java
    • WikiNodeIterable.java

    在运行JedisMaker之前,你必须提供一个文件,关于你的 Redis 服务器信息。如果你在上一个练习中这样做,你应该全部配置好了。否则,你可以在 14.3 节中找到说明。

    运行ant build来编译源文件,然后运行ant JedisMaker来确保它配置为连接到你的 Redis 服务器。

    现在运行ant WikiCrawlerTest。它应该失败,因为你有工作要做!

    这是我提供的WikiCrawler类的起始:

    public class WikiCrawler {
    
        public final String source;
        private JedisIndex index;
        private Queue<String> queue = new LinkedList<String>();
        final static WikiFetcher wf = new WikiFetcher();
    
        public WikiCrawler(String source, JedisIndex index) {
            this.source = source;
            this.index = index;
            queue.offer(source);
        }
    
        public int queueSize() {
            return queue.size();
        }

    实例变量是:

    • source是我们开始抓取的网址。
    • indexJedisIndex,结果应该放进这里。
    • queueLinkedList,这里面我们跟踪已发现但尚未编入索引的网址。
    • wfWikiFetcher,我们用来读取和解析网页。

    你的工作是填写crawl。这是原型:

    public String crawl(boolean testing) throws IOException {}

    当这个方法在WikiCrawlerTest中调用时,testing参数为true,否则为false

    如果testingtruecrawl方法应该:

    • 以 FIFO 的顺序从队列中选择并移除一个 URL。
    • 使用WikiFetcher.readWikipedia读取页面的内容,它读取仓库中包含的,页面的缓存副本来进行测试(如果维基百科的版本更改,则避免出现问题)。
    • 它应该索引页面,而不管它们是否已经被编入索引。
    • 它应该找到页面上的所有内部链接,并按他们出现的顺序将它们添加到队列中。“内部链接”是指其他维基百科页面的链接。
    • 它应该返回其索引的页面的 URL。

    如果testingfalse,这个方法应该:

    • 以 FIFO 的顺序从队列中选择并移除一个 URL。
    • 如果 URL 已经被编入索引,它不应该再次索引,并应该返回null
    • 否则它应该使用WikiFetcher.fetchWikipedia读取页面内容,从 Web 中读取当前内容。
    • 然后,它应该对页面进行索引,将链接添加到队列,并返回其索引的页面的 URL。

    WikiCrawlerTest加载具有大约200个链接的队列,然后调用crawl三次。每次调用后,它将检查队列的返回值和新长度。

    当你的爬虫按规定工作时,此测试应通过。祝你好运!

    展开全文
  • 思维导图

    千次阅读 2014-11-12 21:19:45
     二叉树的抽象数据类型定义(左右子树中的结点具有相同数据类型及层次关系病,  二叉树的遍历操作(前序,中序,后序,层序遍历) 二叉树的储存结构及实现  顺序存储结构:用一维数组存储各结点,并用结点的...
  • 计算思维与一表述计算思维的框架------计算之树1.计算思维 前段时间,看了战德臣老师讲的计算机基础课《大学计算机–计算思维导论》(大学慕课网MOOC),学到了很多有关于计算机方面更加基础的知识,对于一个跨专业...
  • 、细胞自动机 原文:Chapter 5 Cellular Automatons 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 细胞自动机(CA)是一个世界的模型,带有非常简单的物理。 “细胞”的意思是世界被分成一...
  • 刻意练习的三种思维方式——Phodal

    千次阅读 多人点赞 2018-09-29 14:05:42
    如果白天没时间练习,晚上也不能抽出时间练习,长此以往,一年的工作经验就要分成年来用了。 晚上练习的时候要注意:在睡觉前 30~60 分钟停止编码,否则上床时,脑子里可能还是这些代码,容易失眠。万一灵感一来...
  • 思维导图

    千次阅读 2019-05-17 16:00:24
    思维导图又叫心智导图,是表达发散性思维的有效图形思维工具 ,它简单却又很有效,是一实用性的思维工具。 思维导图充分运用左右脑的机能,利用记忆、阅读、思维的规律,协助人们在科学与艺术、逻辑与想象之间...
  • 数据分析的思维方式

    千次阅读 2016-03-07 17:47:05
    首先,我们要知道,什么叫数据分析。其实从数据到信息的这个过程,就是数据分析。数据本身并没有什么价值,有价值的... 那么,在这个从数据到信息的过程中,肯定是有一些固定的思路,或者称之为思维方式。下面给你一一
  • 中国人的思维缺陷

    千次阅读 2011-09-23 23:03:23
    中国人的思维缺陷 童大焕—2011年9月15日星期四 中国人赶时髦喊口号很起劲,连什么是智慧都还没搞清,很多城市已经喊出建设“智慧城市”口号了。智慧(Wisdom)是指辨析判断、发明创造的能力,不只是情感和...
  • Redis大数据类型应用场景

    万次阅读 2018-07-21 16:17:30
    本博客转自java思维导图公众号,大家可以关注,里面有很多料,可以碎片化时间学习。这里主要为了记录归档,日后不断理解,因为看一遍吸收的难以应用自如。 Redis开创了一新的数据存储思路,使用Redis,我们不用在...
  • 《管理学》第章 组织(MOOC+思维导图) 前言 9月28号写了课本上的组织部分(《管理学》第章 组织)。今天写一下MOOC上的笔记。 文章目录《管理学》第章 组织(MOOC+思维导图)前言组织(课本)思维导图MOOC...
  • 六顶思维帽的目的是避免思维混杂,按这种方式,思考者在某一个时间里就可以只按照一模式思考——而不是在某一时刻做全部的事。对此最好的类比是彩色印图...和彩色印图相似,每一顶帽子在地图上表现为一种类型的思考。
  • 逻辑思维

    千次阅读 2017-09-25 16:05:07
    逻辑思维,又称抽象思维,是人的理性认识阶段,人运用概念、判断、推理等思维类型反映事物本质与规律的认识过程。
  • OSPF思维导图.emmx

    2020-09-12 14:30:14
    基于OSPF理论知识,运行原理,特点,防环机制以及4角色,四种类型五种状态,六LSA,七机制。内容作用概述。行业萌新,不喜勿喷
  • 高效的笔记方法——思维导图

    千次阅读 2007-09-10 15:59:00
    朋友,你是否经常参加工作会议或需要组织各种商业会议?... 巴赞就发明了一创新性的笔记方法,叫做“思维导图”。这对传统的笔记方法提出了挑战。思维导图和传统的直线记录方法完全不同。它看上去
  • 、引用数据类型——数组的使用

    千次阅读 2018-04-17 21:24:51
     在之前的学习我们知道八大基本数据类,而我们现在学习的引用数据类型,这里大家先记住引用数据类型,稍后补充。举个例子,我们现在有个需求,想统计每个班级同学的成绩,一个年级有5个班级,每个班级40人,这时候...
  • PHP程序设计思维导图

    2019-02-13 10:15:33
    原创整理 PHP程序设计的思维导图,对于学习 PHP 的新手有一定...第三章:数据类型 第四章:常量和变量 第章:运算符 第六章:表达式和函数 第七章:流程控制语句 第八章:字符串操作 第九章:正则表达式 第十章:数组
  • 五种主流的虚拟化技术

    万次阅读 多人点赞 2017-07-11 17:24:12
    然而尽管到现在各种虚拟化技术还没能泾渭分明,但随着时间的发展,五种主流的虚拟化技术逐步展露。这五种虚拟化技术分别是:CPU虚拟化、网络虚拟化、服务器虚拟化、存储虚拟化和应用虚拟化。  虚拟化,曾经是一个...
  • 思维导图学习总结

    万次阅读 2017-03-22 13:42:26
    思维导图又叫心智导图是表达发散性思维的有效的图形思维工具 ,它简单却又极其有效,是一 革命性的思维工具。思维导图运用图文并重的技巧,把各级主题的关系用相互隶属与相关的层级图 表现出来,把主题关键词...
  • OSPF思维导图理论

    2020-11-11 19:04:33
    ospf原理、ospf四角色、四个网络类型个报文、七状态机、六LSA、ospf区域优化、区域认证命令、网段聚合、选举DR/BDR、基础等等
  • 思维导图”初认识

    万次阅读 2014-09-08 10:25:09
    思维导图  ...一、什么是思维导图 ...、齐伟系列(1):概念图/思维导图导论 六、思维导图相对于线性笔记的好处 七、思维导图课堂教学应用一例子 八、关于思维
  • 五种常用算法

    千次阅读 2015-07-17 17:33:51
     在计算机科学中,分治法是一很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的...
  • 罗辑思维现象透析

    万次阅读 2015-08-14 09:39:13
    微信公众号“罗辑思维”运营时间大概有2年多了,渐渐的也成为了一现象。笔者可以说是它最早的一批用户,从罗辑思维公众号成立不久即开始关注,经历了几个发展阶段,现在做个分析,以飨读者。 以前的互联网做...
  • C++入门教程(三十):函数类型

    千次阅读 2018-04-03 09:27:25
    小古银的官方网站(完整... 目录 目录 函数类型 基础示例 1 基础示例 2 基础讲解 2 基础示例 3 基础讲解 3 补充知识(了解即可) 函数类型 函数实际上也是一个值,也就是说,函数可以用变量来保存,...
  • 突破思维的障碍

    万次阅读 2013-12-16 15:50:13
     在众多的讲述思维及创造性的书中,这是一本普通的小册子,但它却是吸引人的。作者用妙趣横生而又日常可见的素材向我们娓娓叙说了人人都会关心的问题,即我们是否意识到自己的思维障碍,怎样克服它,让自己变得更...
  • 作为一款发散思维的脑图工具,思维导图一经问世便迅速扩散,大量的世界百强企业,如:波音、IBM、Google等都在使用思维导图来工作。可以说思维导图的应用领域是非常广阔的,它在我们的生活中无处不...
  • 程序员喜欢的5小姐姐类型

    千次阅读 2018-08-03 11:45:26
    猿们心中期待的小姐姐都是哪些类型呢? 一、清纯+智慧的小姐姐 这类小姐姐的外表特征:清纯,永远有一颗清醒的大脑,眼睛坚定有神。 她能“吃鸡”能“王者”,上得了厅堂下得了厨房,最优秀的是不粘人。 当猿宝....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 55,870
精华内容 22,348
关键字:

思维的五种类型