# 七、函數和表格
> 原文:[Functions and Tables](https://github.com/data-8/textbook/tree/gh-pages/chapters/07)
> 譯者:[飛龍](https://github.com/wizardforcel)
> 協議:[CC BY-NC-SA 4.0](http://creativecommons.org/licenses/by-nc-sa/4.0/)
> 自豪地采用[谷歌翻譯](https://translate.google.cn/)
通過使用 Python 中已有的函數,我們正在建立一個使用的技術清單,用于識別數據集中的規律和主題。 現在我們將探索Python編程語言的核心功能:函數定義。
我們在本書中已經廣泛使用了函數,但從未定義過我們自己的函數。定義一個函數的目的是,給一個計算過程命名,它可能會使用多次。計算中有許多需要重復計算的情況。 例如,我們常常希望對表的列中的每個值執行相同的操作。
## 定義函數
`double`函數的定義僅僅使一個數值加倍。
```py
# Our first function definition
def double(x):
""" Double x """
return 2*x
```
我們通過編寫`def`來開始定義任何函數。 下面是這個小函數的其他部分(語法)的細分:

當我們運行上面的單元格時,沒有使特定的數字加倍,并且`double`主體中的代碼還沒有求值。因此,我們的函數類似于一個菜譜。 每次我們遵循菜譜中的指導,我們都需要以食材開始。 每次我們想用我們的函數來使一個數字加倍時,我們需要指定一個數字。
我們可以用和調用其他函數完全相同的方式,來調用`double`。 每次我們這樣做的時候,主體中的代碼都會執行,參數的值賦給了名稱`x`。
```py
double(17)
34
double(-0.6/4)
-0.3
```
以上兩個表達式都是調用表達式。 在第二個里面,計算了表達式`-0.6 / 4`的值,然后將其作為參數`x`傳遞給`double`函數。 每個調用表達式最終都會執行`double`的主體,但使用不同的`x`值。
`double`的主體只有一行:
```py
return 2*x
```
執行這個`return`語句會完成`double`函數體的執行,并計算調用表達式的值。
`double`的參數可以是任何表達式,只要它的值是一個數字。 例如,它可以是一個名稱。 `double`函數不知道或不在意如何計算或存儲參數。 它唯一的工作是,使用傳遞給它的參數的值來執行它自己的主體。
```py
any_name = 42
double(any_name)
84
```
參數也可以是任何可以加倍的值。例如,可以將整個數值數組作為參數傳遞給`double`,結果將是另一個數組。
```py
double(make_array(3, 4, 5))
array([ 6, 8, 10])
```
但是,函數內部定義的名稱(包括像double的x這樣的參數)只存在一小會兒。 它們只在函數被調用的時候被定義,并且只能在函數體內被訪問。 我們不能在`double`之外引用`x`。 技術術語是`x`具有局部作用域。
因此,即使我們在上面的單元格中調用了`double`,名稱`x`也不能在函數體外識別。
```py
x
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-18-401b30e3b8b5> in <module>()
----> 1 x
NameError: name 'x' is not defined
```
文檔字符串。 雖然`double`比較容易理解,但是很多函數執行復雜的任務,并且沒有解釋就很難使用。 (你自己也可能已經發現了!)因此,一個組成良好的函數有一個喚起它的行為的名字,以及文檔。 在 Python 中,這被稱為文檔字符串 - 描述了它的行為和對其參數的預期。 文檔字符串也可以展示函數的示例調用,其中調用前面是`>>>`。
文檔字符串可以是任何字符串,只要它是函數體中的第一個東西。 文檔字符串通常在開始和結束處使用三個引號來定義,這允許字符串跨越多行。 第一行通常是函數的完整但簡短的描述,而下面的行則為將來的用戶提供了進一步的指導。
下面是一個名為`percent`的函數定義,它帶有兩個參數。定義包括一個文檔字符串。
```py
# A function with more than one argument
def percent(x, total):
"""Convert x to a percentage of total.
More precisely, this function divides x by total,
multiplies the result by 100, and rounds the result
to two decimal places.
>>> percent(4, 16)
25.0
>>> percent(1, 6)
16.67
"""
return round((x/total)*100, 2)
percent(33, 200)
16.5
```
將上面定義的函數`percent`與下面定義的函數`percents`進行對比。 后者以數組為參數,將數組中的所有數字轉換為數組中所有值的百分數。 百分數都四舍五入到兩位,這次使用`round`來代替`np.round`,因為參數是一個數組而不是一個數字。
```py
def percents(counts):
"""Convert the values in array_x to percents out of the total of array_x."""
total = counts.sum()
return np.round((counts/total)*100, 2)
```
函數`percents`返回一個百分數的數組,除了四舍五入之外,它總計是 100。
```py
some_array = make_array(7, 10, 4)
percents(some_array)
array([ 33.33, 47.62, 19.05])
```
理解 Python 執行函數的步驟是有幫助的。 為了方便起見,我們在下面的同一個單元格中放入了函數定義和對這個函數的調用。
```py
def biggest_difference(array_x):
"""Find the biggest difference in absolute value between two adjacent elements of array_x."""
diffs = np.diff(array_x)
absolute_diffs = abs(diffs)
return max(absolute_diffs)
some_numbers = make_array(2, 4, 5, 6, 4, -1, 1)
big_diff = biggest_difference(some_numbers)
print("The biggest difference is", big_diff)
The biggest difference is 5
```
這就是當我們運行單元格時,所發生的事情。

## 多個參數
可以有多種方式來推廣一個表達式或代碼塊,因此一個函數可以有多個參數,每個參數決定結果的不同方面。 例如,我們以前定義的百分比`percents`,每次都四舍五入到兩位。 以下兩個參的數定義允許不同調用四舍五入到不同的位數。
```py
def percents(counts, decimal_places):
"""Convert the values in array_x to percents out of the total of array_x."""
total = counts.sum()
return np.round((counts/total)*100, decimal_places)
parts = make_array(2, 1, 4)
print("Rounded to 1 decimal place: ", percents(parts, 1))
print("Rounded to 2 decimal places:", percents(parts, 2))
print("Rounded to 3 decimal places:", percents(parts, 3))
Rounded to 1 decimal place: [ 28.6 14.3 57.1]
Rounded to 2 decimal places: [ 28.57 14.29 57.14]
Rounded to 3 decimal places: [ 28.571 14.286 57.143]
```
這個新定義的靈活性來源于一個小的代價:每次調用該函數時,都必須指定小數位數。默認參數值允許使用可變數量的參數調用函數;在調用表達式中未指定的任何參數都被賦予其默認值,這在`def`語句的第一行中進行了說明。 例如,在`percents`的最終定義中,可選參數`decimal_places`賦為默認值`2`。
```py
def percents(counts, decimal_places=2):
"""Convert the values in array_x to percents out of the total of array_x."""
total = counts.sum()
return np.round((counts/total)*100, decimal_places)
parts = make_array(2, 1, 4)
print("Rounded to 1 decimal place:", percents(parts, 1))
print("Rounded to the default number of decimal places:", percents(parts))
Rounded to 1 decimal place: [ 28.6 14.3 57.1]
Rounded to the default number of decimal places: [ 28.57 14.29 57.14]
```
## 注:方法
函數通過將參數表達式放入函數名稱后面的括號來調用。 任何獨立定義的函數都是這樣調用的。 你也看到了方法的例子,這些方法就像函數一樣,但是用點符號來調用,比如`some_table.sort(some_label)`。 你定義的函數將始終首先使用函數名稱,并傳入所有參數來調用。
## 在列上應用函數
我們已經看到很多例子,通過將函數應用于現有列或其他數組,來創建新的表格的列。 所有這些函數都以數組作為參數。 但是我們經常打算,通過一個函數轉換列中的條目,它不將數組作為它的函數。 例如,它可能只需要一個數字作為它的參數,就像下面定義的函數`cut_off_at_100`。
```py
def cut_off_at_100(x):
"""The smaller of x and 100"""
return min(x, 100)
cut_off_at_100(17)
17
cut_off_at_100(117)
100
cut_off_at_100(100)
100
```
如果參數小于或等于 100,函數`cut_off_at_100`只返回它的參數。但是如果參數大于 100,則返回 100。
在我們之前使用人口普查數據的例子中,我們看到變量`AGE`的值為 100,表示“100 歲以上”。 以這種方式將年齡限制在 100 歲,正是`cut_off_at_100`所做的。
為了一次性對很多年齡使用這個函數,我們必須能夠引用函數本身,而不用實際調用它。 類似地,我們可能會向廚師展示一個蛋糕的菜譜,并要求她用它來烤 6 個蛋糕。 在這種情況下,我們不會使用這個配方自己烘烤蛋糕, 我們的角色只是把菜譜給廚師。 同樣,我們可以要求一個表格,在列中的 6 個不同的數字上調用`cut_off_at_100`。
首先,我們創建了一個表,一列是人,一列是它們的年齡。 例如,`C`是 52 歲。
```py
ages = Table().with_columns(
'Person', make_array('A', 'B', 'C', 'D', 'E', 'F'),
'Age', make_array(17, 117, 52, 100, 6, 101)
)
ages
```
| Person | Age |
| --- | --- |
| A | 17 |
| B | 117 |
| C | 52 |
| D | 100 |
| E | 6 |
| F | 101 |
### 應用
要在 100 歲截斷年齡,我們將使用一個新的`Table`方法。 `apply`方法在列的每個元素上調用一個函數,形成一個返回值的新數組。 為了指出要調用的函數,只需將其命名(不帶引號或括號)。 輸入值的列的名稱必須是字符串,仍然出現在引號內。
```py
ages.apply(cut_off_at_100, 'Age')
array([ 17, 100, 52, 100, 6, 100])
```
我們在這里所做的是,將`cut_off_at_100`函數應用于`age`表的`Age`列中的每個值。 輸出是函數的相應返回值的數組。 例如,17 還是 17,117 變成了 100,52 還是 52,等等。
此數組的長度與`age`表中原始`Age`列的長度相同,可用作名為`Cut Off Age`的新列中的值,并與現有的`Person`和`Age`列共存。
```py
ages.with_column(
'Cut Off Age', ages.apply(cut_off_at_100, 'Age')
)
```
| Person | Age | Cut Off Age |
| --- | --- | --- |
| A | 17 | 17 |
| B | 117 | 100 |
| C | 52 | 52 |
| D | 100 | 100 |
| E | 6 | 6 |
| F | 101 | 100 |
### 作為值的函數
我們已經看到,Python 有很多種值。 例如,`6`是一個數值,`"cake"`是一個文本值,`Table()`是一個空表,`age`是一個表值(因為我們在上面定義)的名稱。
在 Python 中,每個函數(包括`cut_off_at_100`)也是一個值。 這有助于再次考慮菜譜。 蛋糕的菜譜是一個真實的東西,不同于蛋糕或配料,你可以給它一個名字,像“阿尼的蛋糕菜譜”。 當我們用`def`語句定義`cut_off_at_100`時,我們實際上做了兩件事情:我們創建了一個函數來截斷數字 100,我們給它命名為`cut_off_at_100`。
我們可以引用任何函數,通過寫下它的名字,而沒有實際調用它必需的括號或參數。當我們在上面調用`apply`時,我們做了這個。 當我們自己寫下一個函數的名字,作為單元格中的最后一行時,Python 會生成一個函數的文本表示,就像打印一個數字或一個字符串值一樣。
```py
cut_off_at_100
<function __main__.cut_off_at_100>
```
請注意,我們沒有使用引號(它只是一段文本)或`cut_off_at_100()`(它是一個函數調用,而且是無效的)。我們只是寫下`cut_off_at_100`來引用這個函數。
就像我們可以為其他值定義新名稱一樣,我們可以為函數定義新名稱。 例如,假設我們想把我們的函數稱為`cut_off`,而不是`cut_off_at_100`。 我們可以這樣寫:
```py
cut_off = cut_off_at_100
```
現在`cut_off`就是函數名稱了。它是`cut_off_at_100`的相同函數。所以打印出的值應該相同。
```py
cut_off
<function __main__.cut_off_at_100>
```
讓我們看看另一個`apply`的應用。
### 示例:預測
數據科學經常用來預測未來。 如果我們試圖預測特定個體的結果 - 例如,她將如何回應處理方式,或者他是否會購買產品,那么將預測基于其他類似個體的結果是很自然的。
查爾斯·達爾文(Charles Darwin)的堂兄弗朗西斯·高爾頓(Sir Francis Galton)是使用這個思想來基于數值數據進行預測的先驅。 他研究了物理特征是如何傳遞下來的。
下面的數據是父母和他們的成年子女的身高測量值,由高爾頓仔細收集。 每行對應一個成年子女。 變量是家庭的數字代碼,父母的身高(以英寸為單位),“雙親身高”,這是父母雙方身高的加權平均值 [1],家庭中子女的數量 ,以及子女的出生次序(第幾個),性別和身高。
> [1] 高爾頓在計算男性和女性的平均身高之前,將女性身高乘上 1.08。對于這個的討論,請查看 [Chance](http://chance.amstat.org/2013/09/1-pagano/),這是一個由美國統計協會出版的雜志。
```py
# Galton's data on heights of parents and their adult children
galton = Table.read_table('galton.csv')
galton
```
| family | father | mother | midparentHeight | children | childNum | gender | childHeight |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | 78.5 | 67 | 75.43 | 4 | 1 | male | 73.2 |
| 1 | 78.5 | 67 | 75.43 | 4 | 2 | female | 69.2 |
| 1 | 78.5 | 67 | 75.43 | 4 | 3 | female | 69 |
| 1 | 78.5 | 67 | 75.43 | 4 | 4 | female | 69 |
| 2 | 75.5 | 66.5 | 73.66 | 4 | 1 | male | 73.5 |
| 2 | 75.5 | 66.5 | 73.66 | 4 | 2 | male | 72.5 |
| 2 | 75.5 | 66.5 | 73.66 | 4 | 3 | female | 65.5 |
| 2 | 75.5 | 66.5 | 73.66 | 4 | 4 | female | 65.5 |
| 3 | 75 | 64 | 72.06 | 2 | 1 | male | 71 |
| 3 | 75 | 64 | 72.06 | 2 | 2 | female | 68 |
(省略了 924 行)
收集數據的主要原因是,能夠預測父母所生的子女的成年身高,其中父母和數據集中的類似。讓我們嘗試這樣做,用雙親的身高作為我們預測的基礎變量。 因此雙親的身高是我們的預測性變量。
表格`heights`包含雙親和子女的身高。 兩個變量的散點圖顯示了正相關,正如我們對這些變量的預期。
```py
heights = galton.select(3, 7).relabeled(0, 'MidParent').relabeled(1, 'Child')
heights
```
| MidParent | Child |
| --- | --- |
| 75.43 | 73.2 |
| 75.43 | 69.2 |
| 75.43 | 69 |
| 75.43 | 69 |
| 73.66 | 73.5 |
| 73.66 | 72.5 |
| 73.66 | 65.5 |
| 73.66 | 65.5 |
| 72.06 | 71 |
| 72.06 | 68 |
(省略了 924 行)
```py
heights.scatter(0)
```

現在假設高爾頓遇到了新的一對夫婦,與他的數據集類似,并且想知道他們的子女有多高。考慮到雙親身高是 68 英寸,他預測子女身高的一個好方法是什么?
一個合理的方法是基于約 68 英寸的雙親身高對應的所有點,來做預測。預測值等于從這些點計算的子女身高的均值。
假設我們是高爾頓,并執行這個計劃。現在我們只是對“68 英寸左右”的含義做一個合理的定義,并用它來處理。在課程的后面,我們將研究這種選擇的后果。
我們的“接近”的意思是“在半英寸之內”。下圖顯示了 67.5 英寸和 68.5 英寸之間的雙親身高對應的所有點。這些都是紅色直線之間的點。每一個點都對應一個子女;我們對新夫婦的子女身高的預測是所有子女的平均身高。這由金色的點表示。
忽略代碼,僅僅專注于理解到達金色的點的心理過程。
```py
heights.scatter('MidParent')
_ = plots.plot([67.5, 67.5], [50, 85], color='red', lw=2)
_ = plots.plot([68.5, 68.5], [50, 85], color='red', lw=2)
_ = plots.scatter(68, 66.24, color='gold', s=40)
```

為了準確計算出金色的點的位置,我們首先需要確定直線之間的所有點。 這些點對應于`MidParent`在 67.5 英寸和 68.5 英寸之間的行。
```py
close_to_68 = heights.where('MidParent', are.between(67.5, 68.5))
close_to_68
```
| MidParent | Child |
| --- | --- |
| 68.44 | 62 |
| 67.94 | 71.2 |
| 67.94 | 67 |
| 68.33 | 62.5 |
| 68.23 | 73 |
| 68.23 | 72 |
| 68.23 | 69 |
| 67.98 | 73 |
| 67.98 | 71 |
| 67.98 | 71 |
(省略了 121 行)
雙親身高為 68 英寸的子女的預測身高,是這些行中子女的平均身高。 這是 66.24 英寸。
```py
close_to_68.column('Child').mean()
66.24045801526718
```
我們現在有了一種方法,給定任何數據集中的雙親身高,就可以預測子女的身高。我們可以定義一個函數`predict_child`來實現它。 除了名稱的選擇之外,函數的主體由上面兩個單元格中的代碼組成。
```py
def predict_child(mpht):
"""Predict the height of a child whose parents have a midparent height of mpht.
The prediction is the average height of the children whose midparent height is
in the range mpht plus or minus 0.5.
"""
close_points = heights.where('MidParent', are.between(mpht-0.5, mpht + 0.5))
return close_points.column('Child').mean()
```
給定 68 英寸的雙親身高,函數`predict_child`返回與之前相同的預測(66.24 英寸)。 定義函數的好處在于,我們可以很容易地改變預測變量的值,并得到一個新的預測結果。
```py
predict_child(68)
66.24045801526718
predict_child(74)
70.415789473684214
```
這些預測有多好? 我們可以了解它,通過將預測值與我們已有的數據進行比較。 為此,我們首先將函數`predict_child`應用于`Midparent`列,并將結果收入稱為`Prediction`的新列中。
```py
# Apply predict_child to all the midparent heights
heights_with_predictions = heights.with_column(
'Prediction', heights.apply(predict_child, 'MidParent')
)
heights_with_predictions
```
| MidParent | Child | Prediction |
| --- | --- | --- |
| 75.43 | 73.2 | 70.1 |
| 75.43 | 69.2 | 70.1 |
| 75.43 | 69 | 70.1 |
| 75.43 | 69 | 70.1 |
| 73.66 | 73.5 | 70.4158 |
| 73.66 | 72.5 | 70.4158 |
| 73.66 | 65.5 | 70.4158 |
| 73.66 | 65.5 | 70.4158 |
| 72.06 | 71 | 68.5025 |
| 72.06 | 68 | 68.5025 |
(省略了 924 行)
為了查看預測值相對于觀察數據的位置,可以使用`MidParent`作為公共水平軸繪制重疊的散點圖。
```py
heights_with_predictions.scatter('MidParent')
```

金色的點的圖形稱為均值圖,因為每個金色的點都是兩條直線的中心,就像之前繪制的那樣。每個都按照給定的雙親高度,做出了子女高度的預測。例如,散點圖顯示,對于 72 英寸的雙親高度,子女的預測高度將在 68 英寸和 69 英寸之間,事實上,`predict_child(72)`返回 68.5。
高爾頓的計算和可視化與我們非常相似,除了他沒有 Python。他通過散點圖繪制了均值圖,并注意到它大致沿著直線。這條直線現在被稱為回歸線,是最常見的預測方法之一。高爾頓的朋友,數學家卡爾·皮爾森(Karl Pearson)用這些分析來形式化關聯的概念。
這個例子,就像約翰·斯諾(John Snow)對霍亂死亡的分析一樣,說明了現代數據科學的一些基本概念的根源可追溯到一個多世紀之前。高爾頓的方法,比如我們在這里使用的方法,是最近鄰預測方法的雛形,現在在不同的環境中有著有效的應用。機器學習的現代領域包括這些方法的自動化,來基于龐大且快速發展的數據集進行預測。
## 按照單變量分類
數據科學家經常需要根據共有的特征,將個體分成不同的組,然后確定組的一些特征。 例如,在使用高爾頓高度數據的例子中,我們看到根據父母的平均高度對家庭進行分類,然后找出每個小組中子女的平均身高,較為實用。
這部分關于將個體分類到非數值類別。我們從回顧`gourp`的基本用法開始。
### 計算每個分類的數量
具有單個參數的`group `方法計算列中每個值的數量。 結果中,分組列(用于分組的列)中的每個唯一值是一行。
這是一個關于冰淇淋圓通的小型數據表。 `group `方法可以用來列出不同的口味,并提供每種口味的計數。
```py
cones = Table().with_columns(
'Flavor', make_array('strawberry', 'chocolate', 'chocolate', 'strawberry', 'chocolate'),
'Price', make_array(3.55, 4.75, 6.55, 5.25, 5.25)
)
cones
```
| Flavor | Price |
| --- | --- |
| strawberry | 3.55 |
| chocolate | 4.75 |
| chocolate | 6.55 |
| strawberry | 5.25 |
| chocolate | 5.25 |
```py
cones.group('Flavor')
```
| Flavor | count |
| --- | --- |
| chocolate | 3 |
| strawberry | 2 |
有兩個不同的類別,巧克力和草莓。 `group`的調用會在每個類別中創建一個計數表。 該列默認稱為`count`,并包含每個類別中的行數。
注意,這一切都可以從`Flavor`列中找到。`Price `列尚未使用。
但是如果我們想要每種不同風味的圓筒的總價格呢? 這是`group`的第二個參數的作用。
### 發現每個類別的特征
`group`的可選的第二個參數是一個函數,用于聚合所有這些行的其他列中的值。 例如,`sum`將累計與每個類別匹配的所有行中的價格。 這個結果中,分組列中每個唯一值是一行,但與原始表列數相同。
為了找到每種口味的總價格,我們再次調用`group`,用`Flavor`作為第一個參數。 但這一次有第二個參數:函數名稱`sum`。
```py
cones.group('Flavor', sum)
```
| Flavor | Price sum |
| --- | --- |
| chocolate | 16.55 |
| strawberry | 8.8 |
為了創建這個新表格,`group`已經計算了對應于每種不同口味的,所有行中的`Price`條目的總和。 三個`chocolate`行的價格共計`$16.55`(你可以假設價格是以美元計量的)。 兩個`strawberry`行的價格共計`8.80`。
新創建的“總和”列的標簽是`Price sum`,它通過使用被求和列的標簽,并且附加單詞`sum`創建。
由于`group`計算除了類別之外的所有列的`sum`,因此不需要指定必須對價格求和。
為了更詳細地了解`group`在做什么,請注意,你可以自己計算總價格,不僅可以通過心算,還可以使用代碼。 例如,要查找所有巧克力圓筒的總價格,你可以開始創建一個僅包含巧克力圓筒的新表,然后訪問價格列:
```py
cones.where('Flavor', are.equal_to('chocolate')).column('Price')
array([ 4.75, 6.55, 5.25])
sum(cones.where('Flavor', are.equal_to('chocolate')).column('Price'))
16.550000000000001
```
這就是`group `對`Flavor`中每個不同的值所做的事情。
```py
# For each distinct value in `Flavor, access all the rows
# and create an array of `Price`
cones_choc = cones.where('Flavor', are.equal_to('chocolate')).column('Price')
cones_strawb = cones.where('Flavor', are.equal_to('strawberry')).column('Price')
# Display the arrays in a table
grouped_cones = Table().with_columns(
'Flavor', make_array('chocolate', 'strawberry'),
'Array of All the Prices', make_array(cones_choc, cones_strawb)
)
# Append a column with the sum of the `Price` values in each array
price_totals = grouped_cones.with_column(
'Sum of the Array', make_array(sum(cones_choc), sum(cones_strawb))
)
price_totals
```
| Flavor | Array of All the Prices | Sum of the Array |
| --- | --- | --- |
| chocolate | [ 4.75 6.55 5.25] | 16.55 |
| strawberry | [ 3.55 5.25] | 8.8 |
你可以用任何其他可以用于數組的函數來替換`sum`。 例如,你可以使用`max`來查找每個類別中的最大價格:
```py
cones.group('Flavor', max)
```
| Flavor | Price max |
| --- | --- |
| chocolate | 6.55 |
| strawberry | 5.25 |
同樣,`group`在每個`Flavor`分類中創建價格數組,但現在它尋找每個數組的`max `。
```py
price_maxes = grouped_cones.with_column(
'Max of the Array', make_array(max(cones_choc), max(cones_strawb))
)
price_maxes
```
| Flavor | Array of All the Prices | Max of the Array |
| --- | --- | --- |
| chocolate | [ 4.75 6.55 5.25] | 6.55 |
| strawberry | [ 3.55 5.25] | 5.25 |
實際上,只有一個參數的原始調用,與使用`len`作為函數并清理表格的效果相同。
```py
lengths = grouped_cones.with_column(
'Length of the Array', make_array(len(cones_choc), len(cones_strawb))
)
lengths
```
| Flavor | Array of All the Prices | Length of the Array |
| --- | --- | --- |
| chocolate | [ 4.75 6.55 5.25] | 3 |
| strawberry | [ 3.55 5.25] | 2 |
### 示例:NBA 薪水
`nba`表包含了 2015~2016 年 NBA 球員的數據。 我們早些時候審查了這些數據。 回想一下,薪水以百萬美元計算。
```py
nba1 = Table.read_table('nba_salaries.csv')
nba = nba1.relabeled("'15-'16 SALARY", 'SALARY')
nba
```
| PLAYER | POSITION | TEAM | SALARY |
| --- | --- | --- | --- |
| Paul Millsap | PF | Atlanta Hawks | 18.6717 |
| Al Horford | C | Atlanta Hawks | 12 |
| Tiago Splitter | C | Atlanta Hawks | 9.75625 |
| Jeff Teague | PG | Atlanta Hawks | 8 |
| Kyle Korver | SG | Atlanta Hawks | 5.74648 |
| Thabo Sefolosha | SF | Atlanta Hawks | 4 |
| Mike Scott | PF | Atlanta Hawks | 3.33333 |
| Kent Bazemore | SF | Atlanta Hawks | 2 |
| Dennis Schroder | PG | Atlanta Hawks | 1.7634 |
| Tim Hardaway Jr. | SG | Atlanta Hawks | 1.30452 |
(省略了 407 行)
(1)每支球隊為球員的工資支付了多少錢?
唯一涉及的列是`TEAM`和`SALARY`。 我們必須按`TEAM`對這些行進行分組,然后對這些分類的工資進行求和。
```py
teams_and_money = nba.select('TEAM', 'SALARY')
teams_and_money.group('TEAM', sum)
```
| TEAM | SALARY sum |
| --- | --- |
| Atlanta Hawks | 69.5731 |
| Boston Celtics | 50.2855 |
| Brooklyn Nets | 57.307 |
| Charlotte Hornets | 84.1024 |
| Chicago Bulls | 78.8209 |
| Cleveland Cavaliers | 102.312 |
| Dallas Mavericks | 65.7626 |
| Denver Nuggets | 62.4294 |
| Detroit Pistons | 42.2118 |
| Golden State Warriors | 94.0851 |
(省略了 20 行)
(2)五個位置的每個中有多少個 NBA 球員呢?
我們必須按`POSITION`分類并計數。 這可以通過一個參數來完成:
```py
nba.group('POSITION')
```
| POSITION | count |
| --- | --- |
| C | 69 |
| PF | 85 |
| PG | 85 |
| SF | 82 |
| SG | 96 |
(3)五個位置的每個中,球員平均薪水是多少?
這一次,我們必須按`POSITION`分組,并計算薪水的均值。 為了清楚起見,我們將用一張表格來描述位置和薪水。
```py
positions_and_money = nba.select('POSITION', 'SALARY')
positions_and_money.group('POSITION', np.mean)
```
| POSITION | SALARY mean |
| --- | --- |
| C | 6.08291 |
| PF | 4.95134 |
| PG | 5.16549 |
| SF | 5.53267 |
| SG | 3.9882 |
中鋒是最高薪的職位,均值超過 600 萬美元。
如果我們開始沒有選擇這兩列,那么`group`不會嘗試對`nba`中的類別列計算“平均”。 (“亞特蘭大老鷹”和“波士頓凱爾特人隊”這兩個字符串是不可能平均)。它只對數值列做算術,其余的都是空白的。
```py
nba.group('POSITION', np.mean)
```
| POSITION | PLAYER mean | TEAM mean | SALARY mean |
| --- | --- | --- | --- |
| C | | | 6.08291 |
| PF | | | 4.95134 |
| PG | | | 5.16549 |
| SF | | | 5.53267 |
| SG | | | 3.9882 |
## 交叉分類
### 通過多個變量的交叉分類
當個體具有多個特征時,有很多不同的對他們分類的方法。 例如,如果我們有大學生的人口數據,對于每個人我們都有專業和大學的年數,那么這些學生就可以按照專業,按年份,或者是專業和年份的組合來分類。
`group`方法也允許我們根據多個變量劃分個體。 這被稱為交叉分類。
## 兩個變量:計算每個類別偶對的數量
`more_cones`表記錄了六個冰淇淋圓筒的味道,顏色和價格。
```py
more_cones = Table().with_columns(
'Flavor', make_array('strawberry', 'chocolate', 'chocolate', 'strawberry', 'chocolate', 'bubblegum'),
'Color', make_array('pink', 'light brown', 'dark brown', 'pink', 'dark brown', 'pink'),
'Price', make_array(3.55, 4.75, 5.25, 5.25, 5.25, 4.75)
)
more_cones
```
| Flavor | Color | Price |
| --- | --- | --- |
| strawberry | pink | 3.55 |
| chocolate | light brown | 4.75 |
| chocolate | dark brown | 5.25 |
| strawberry | pink | 5.25 |
| chocolate | dark brown | 5.25 |
| bubblegum | pink | 4.75 |
我們知道如何使用`group`,來計算每種口味的冰激凌圓筒的數量。
```py
more_cones.group('Flavor')
```
| Flavor | count |
| --- | --- |
| bubblegum | 1 |
| chocolate | 3 |
| strawberry | 2 |
但是現在每個圓筒也有一個顏色。 為了將圓筒按風味和顏色進行分類,我們將把標簽列表作為參數傳遞給`group`。 在分組列中出現的每個唯一值的組合,在生成的表格中都占一行。 和以前一樣,一個參數(這里是一個列表,但是也可以是一個數組)提供了行數。
雖然有六個圓筒,但只有四種風味和顏色的唯一組合。 兩個圓筒是深褐色的巧克力,還有兩個粉紅色的草莓。
```py
more_cones.group(['Flavor', 'Color'])
```
| Flavor | Color | count |
| --- | --- | --- |
| bubblegum | pink | 1 |
| chocolate | dark brown | 2 |
| chocolate | light brown | 1 |
| strawberry | pink | 2 |
## 兩個變量:查找每個類別偶對的特征
第二個參數聚合所有其他列,它們不在分組列的列表中。
```py
more_cones.group(['Flavor', 'Color'], sum)
```
| Flavor | Color | Price sum |
| --- | --- | --- |
| bubblegum | pink | 4.75 |
| chocolate | dark brown | 10.5 |
| chocolate | light brown | 4.75 |
| strawberry | pink | 8.8 |
三個或更多的變量。 你可以使用`group`,按三個或更多類別變量對行分類。 只要將它們全部包含列表中,它是第一個參數。 但是由多個變量交叉分類可能會變得復雜,因為不同類別組合的數量可能相當大。
### 數據透視表:重新排列`group`的輸出
交叉分類的許多使用只涉及兩個類別變量,如上例中的`Flavor`和`Color`。 在這些情況下,可以在不同類型的表中顯示分類結果,稱為數據透視表(pivot table)。 數據透視表,也被稱為列聯表(contingency table),可以更容易地處理根據兩個變量進行分類的數據。
回想一下,使用`group `來計算每個風味和顏色的類別偶對的圓筒數量:
```py
more_cones.group(['Flavor', 'Color'])
```
| Flavor | Color | count |
| --- | --- | --- |
| bubblegum | pink | 1 |
| chocolate | dark brown | 2 |
| chocolate | light brown | 1 |
| strawberry | pink | 2 |
使用`Table`的`pivot`方法可以以不同方式展示相同數據。暫時忽略這些代碼,然后查看所得表。
```py
more_cones.pivot('Flavor', 'Color')
```
| Color | bubblegum | chocolate | strawberry |
| --- | --- | --- | --- |
| dark brown | 0 | 2 | 0 |
| light brown | 0 | 1 | 0 |
| pink | 1 | 0 | 2 |
請注意,此表格顯示了所有九種可能的風味和顏色偶對,包括我們的數據中不存在的偶對,比如“深棕色泡泡糖”。 還要注意,每個偶對中的計數都出現在表格的正文中:要找到淺棕色巧克力圓筒的數量,用眼睛沿著淺棕色的行看,直到它碰到巧克力一列。
`group`方法接受兩個標簽的列表,因為它是靈活的:可能需要一個或三個或更多。 另一方面,數據透視圖總是需要兩個列標簽,一個確定列,一個確定行。
`pivot`方法與`group`方法密切相關:`group`將擁有相同值的組合的行分組在一起。它與`group`不同,因為它將所得值組織在一個網格中。 `pivot`的第一個參數是列標簽,包含的值將用于在結果中形成新的列。第二個參數是用于行的列標簽。結果提供了原始表的所有行的計數,它們擁有相同的行和列值組合。
像`group`一樣,`pivot`可以和其他參數一同使用,來發現每個類別組合的特征。名為`values`的第三個可選參數表示一列值,它們替換網格的每個單元格中的計數。所有這些值將不會顯示,但是;第四個參數`collect`表示如何將它們全部匯總到一個聚合值中,來顯示在單元格中。
用例子來澄清這一點。這里是一個透視表,用于尋找每個單元格中的圓筒的總價格。
```py
more_cones.pivot('Flavor', 'Color', values='Price', collect=sum)
```
| Color | bubblegum | chocolate | strawberry |
| --- | --- | --- | --- |
| dark brown | 0 | 10.5 | 0 |
| light brown | 0 | 4.75 | 0 |
| pink | 4.75 | 0 | 8.8 |
這里`group `做了同一件事。
```py
more_cones.group(['Flavor', 'Color'], sum)
```
| Flavor | Color | Price sum |
| --- | --- | --- |
| bubblegum | pink | 4.75 |
| chocolate | dark brown | 10.5 |
| chocolate | light brown | 4.75 |
| strawberry | pink | 8.8 |
盡管兩個表中的數字都相同,但由`pivot`生成的表格更易于閱讀,因而更易于分析。 透視表的優點是它將分組的值放到相鄰的列中,以便它們可以進行組合和比較。
### 示例:加州成人的教育和收入
加州的開放數據門戶是豐富的加州生活的信息來源。 這是 2008 至 2014 年間加利福尼亞州教育程度和個人收入的數據集。數據來源于美國人口普查的當前人口調查。
對于每年,表格都記錄了加州的`Population Count`(人口數量),按照年齡,性別,教育程度和個人收入,構成不同的組合。 我們將只研究 2014 年的數據。
```py
full_table = Table.read_table('educ_inc.csv')
ca_2014 = full_table.where('Year', are.equal_to('1/1/14 0:00')).where('Age', are.not_equal_to('00 to 17'))
ca_2014
```
| Year | Age | Gender | Educational Attainment | Personal Income | Population Count |
| --- | --- | --- | --- | --- | --- |
| 1/1/14 0:00 | 18 to 64 | Female | No high school diploma | H: 75,000 and over | 2058 |
| 1/1/14 0:00 | 65 to 80+ | Male | No high school diploma | H: 75,000 and over | 2153 |
| 1/1/14 0:00 | 65 to 80+ | Female | No high school diploma | G: 50,000 to 74,999 | 4666 |
| 1/1/14 0:00 | 65 to 80+ | Female | High school or equivalent | H: 75,000 and over | 7122 |
| 1/1/14 0:00 | 65 to 80+ | Female | No high school diploma | F: 35,000 to 49,999 | 7261 |
| 1/1/14 0:00 | 65 to 80+ | Male | No high school diploma | G: 50,000 to 74,999 | 8569 |
| 1/1/14 0:00 | 18 to 64 | Female | No high school diploma | G: 50,000 to 74,999 | 14635 |
| 1/1/14 0:00 | 65 to 80+ | Male | No high school diploma | F: 35,000 to 49,999 | 15212 |
| 1/1/14 0:00 | 65 to 80+ | Male | College, less than 4-yr degree | B: 5,000 to 9,999 | 15423 |
| 1/1/14 0:00 | 65 to 80+ | Female | Bachelor's degree or higher | A: 0 to 4,999 | 15459 |
(省略了 117 行)
表中的每一行對應一組年齡,性別,教育程度和收入。 總共有 127 個這樣的組合!
作為第一步,從一個或兩個變量開始是個好主意。 我們只關注一對:教育程度和個人收入。
```py
educ_inc = ca_2014.select('Educational Attainment', 'Personal Income', 'Population Count')
educ_inc
```
| Educational Attainment | Personal Income | Population Count |
| --- | --- | --- |
| No high school diploma | H: 75,000 and over | 2058 |
| No high school diploma | H: 75,000 and over | 2153 |
| No high school diploma | G: 50,000 to 74,999 | 4666 |
| High school or equivalent | H: 75,000 and over | 7122 |
| No high school diploma | F: 35,000 to 49,999 | 7261 |
| No high school diploma | G: 50,000 to 74,999 | 8569 |
| No high school diploma | G: 50,000 to 74,999 | 14635 |
| No high school diploma | F: 35,000 to 49,999 | 15212 |
| College, less than 4-yr degree | B: 5,000 to 9,999 | 15423 |
| Bachelor's degree or higher | A: 0 to 4,999 | 15459 |
(省略了 117 行)
我們先看看教育程度。 這個變量的分類已經由不同的收入水平細分了。 因此,我們將按照教育程度分組,并將每個分類中的人口數量相加。
```py
education = educ_inc.select('Educational Attainment', 'Population Count')
educ_totals = education.group('Educational Attainment', sum)
educ_totals
```
| Educational Attainment | Population Count sum |
| --- | --- |
| Bachelor's degree or higher | 8525698 |
| College, less than 4-yr degree | 7775497 |
| High school or equivalent | 6294141 |
| No high school diploma | 4258277 |
教育程度只有四類。 計數太大了,查看百分比更有幫助。 為此,我們將使用前面章節中定義的函數`percents`。 它將數值數組轉換為輸入數組總量的百分比數組。
```py
def percents(array_x):
return np.round( (array_x/sum(array_x))*100, 2)
```
我們現在有加州成人的教育程度分布。 超過 30% 的人擁有學士或更高學位,而幾乎 16% 沒有高中文憑。
```py
educ_distribution = educ_totals.with_column(
'Population Percent', percents(educ_totals.column(1))
)
educ_distribution
```
| Educational Attainment | Population Count sum | Population Percent |
| --- | --- | --- |
| Bachelor's degree or higher | 8525698 | 31.75 |
| College, less than 4-yr degree | 7775497 | 28.96 |
| High school or equivalent | 6294141 | 23.44 |
| No high school diploma | 4258277 | 15.86 |
通過使用`pivot`,我們可以得到一張加州成人的透視表(計數表),按照`Educational Attainment`和`Personal Income`交叉分類。
```py
totals = educ_inc.pivot('Educational Attainment', 'Personal Income', values='Population Count', collect=sum)
totals
```
| Personal Income | Bachelor's degree or higher | College, less than 4-yr degree | High school or equivalent | No high school diploma |
| --- | --- | --- | --- | --- |
| A: 0 to 4,999 | 575491 | 985011 | 1161873 | 1204529 |
| B: 5,000 to 9,999 | 326020 | 810641 | 626499 | 597039 |
| C: 10,000 to 14,999 | 452449 | 798596 | 692661 | 664607 |
| D: 15,000 to 24,999 | 773684 | 1345257 | 1252377 | 875498 |
| E: 25,000 to 34,999 | 693884 | 1091642 | 929218 | 464564 |
| F: 35,000 to 49,999 | 1122791 | 1112421 | 782804 | 260579 |
| G: 50,000 to 74,999 | 1594681 | 883826 | 525517 | 132516 |
| H: 75,000 and over | 2986698 | 748103 | 323192 | 58945 |
在這里你可以看到`pivot`相比其他方法的威力。 計數的每一列都是個人收入在特定教育程度中的分布。 將計數轉換為百分數可以讓我們比較四個分布。
```py
distributions = totals.select(0).with_columns(
"Bachelor's degree or higher", percents(totals.column(1)),
'College, less than 4-yr degree', percents(totals.column(2)),
'High school or equivalent', percents(totals.column(3)),
'No high school diploma', percents(totals.column(4))
)
distributions
```
| Personal Income | Bachelor's degree or higher | College, less than 4-yr degree | High school or equivalent | No high school diploma |
| --- | --- | --- | --- | --- |
| A: 0 to 4,999 | 6.75 | 12.67 | 18.46 | 28.29 |
| B: 5,000 to 9,999 | 3.82 | 10.43 | 9.95 | 14.02 |
| C: 10,000 to 14,999 | 5.31 | 10.27 | 11 | 15.61 |
| D: 15,000 to 24,999 | 9.07 | 17.3 | 19.9 | 20.56 |
| E: 25,000 to 34,999 | 8.14 | 14.04 | 14.76 | 10.91 |
| F: 35,000 to 49,999 | 13.17 | 14.31 | 12.44 | 6.12 |
| G: 50,000 to 74,999 | 18.7 | 11.37 | 8.35 | 3.11 |
| H: 75,000 and over | 35.03 | 9.62 | 5.13 | 1.38 |
一眼就能看出,超過 35% 的學士或以上學位的收入達到 $75,000 美元以上,而其他教育分類中,少于 10% 的人達到了這一水平。
下面的條形圖比較了沒有高中文憑的加州成年人的個人收入分布情況,和完成學士或更高學位的人的收入分布情況。 分布的差異是驚人的。 教育程度與個人收入有明顯的正相關關系。
```py
distributions.select(0, 1, 4).barh(0)
```

## 按列連接表
通常,同一個人的數據在多個表格中維護。 例如,大學的一個辦公室可能有每個學生完成學位的時間的數據,而另一個辦公室則有學生學費和經濟援助的數據。
為了了解學生的經歷,將兩個數據集放在一起可能會有幫助。 如果數據是在兩個表中,每個學生都有一行,那么我們希望將這些列放在一起,確保行是匹配的,以便將每個學生的信息保持在一行上。
讓我們在一個簡單的示例的背景下實現它,然后在更大的數據集上使用這個方法。
圓筒表是我們以前遇到的。 現在假設每種口味的冰激凌的評分都在獨立的表格中。
```py
cones = Table().with_columns(
'Flavor', make_array('strawberry', 'vanilla', 'chocolate', 'strawberry', 'chocolate'),
'Price', make_array(3.55, 4.75, 6.55, 5.25, 5.75)
)
cones
```
| Flavor | Price |
| --- | --- |
| strawberry | 3.55 |
| vanilla | 4.75 |
| chocolate | 6.55 |
| strawberry | 5.25 |
| chocolate | 5.75 |
```py
ratings = Table().with_columns(
'Kind', make_array('strawberry', 'chocolate', 'vanilla'),
'Stars', make_array(2.5, 3.5, 4)
)
ratings
```
| Kind | Stars |
| --- | --- |
| strawberry | 2.5 |
| chocolate | 3.5 |
| vanilla | 4 |
每個表都有一個包含冰淇淋風味的列:`cones`有`Flavor`列,`ratings `有`Kind`列。 這些列中的條目可以用來連接兩個表。
`join`方法創建一個新的表,其中`cones`表中的每個圓筒都增加了評分信息。 對于`cones`中的每個圓筒,`join`會找到`ratings `中的行,它的`Kind `匹配圓筒的`Flavor`。 我們必須告訴`join`使用這些列進行匹配。
```py
rated = cones.join('Flavor', ratings, 'Kind')
rated
```
| Flavor | Price | Stars |
| --- | --- | --- |
| chocolate | 6.55 | 3.5 |
| chocolate | 5.75 | 3.5 |
| strawberry | 3.55 | 2.5 |
| strawberry | 5.25 | 2.5 |
| vanilla | 4.75 | 4 |
現在每個圓筒不僅擁有價格,而且還有風味評分。
一般來說,使用來自另一個表(比如`table2`)的信息來擴充一個表(如`table1`)的`join `調用如下所示:
```py
table1.join(table1_column_for_joining, table2, table2_column_for_joining)
```
新的`rated`表使我們能夠計算出價格與星星的比值,你可以把它看作是非正式的價值衡量標準。 低的值更好 - 它們意味著你為評分的每個星星花費更少。
```py
rated.with_column('$/Star', rated.column('Price') / rated.column('Stars')).sort(3)
```
| Flavor | Price | Stars | $/Star |
| --- | --- | --- | --- |
| vanilla | 4.75 | 4 | 1.1875 |
| strawberry | 3.55 | 2.5 | 1.42 |
| chocolate | 5.75 | 3.5 | 1.64286 |
| chocolate | 6.55 | 3.5 | 1.87143 |
| strawberry | 5.25 | 2.5 | 2.1 |
雖然草莓在這三種口味中評分最低,但是這個標準下草莓更加便宜,因為每顆星星的花費并不高。
警告。順序很重要。由于`join`中的第二個表用于擴充第一個表,所以重要的是,第一個表中的每一行在第二個表中只有一個匹配的行。如果第一個表中的某一行在第二個表中沒有匹配項,則信息可能丟失。如果第一個表中的某一行在第二個表中有多個匹配項,那么`join`將只選擇一個,這也是一種信息丟失。
我們可以在下面的例子中看到它,它試圖通過相同的兩列連接相同的兩個表格,但是以另一種順序。這種連接是沒有意義的:它試圖用價格來擴展每種風味的評分,但是根據`cones`表,每種風味都有一個以上的圓筒(和價格)。結果是兩個圓筒消失了。`join`方法僅僅在`cones`尋找對應`chocolate`的第一行,而忽略其他行。
```py
ratings.join('Kind', cones, 'Flavor')
```
| Kind | Stars | Price |
| --- | --- | --- |
| chocolate | 3.5 | 6.55 |
| strawberry | 2.5 | 3.55 |
| vanilla | 4 | 4.75 |
假設有個冰淇淋的評分表,我們已經求出了每種風味的平均評分。
```py
reviews = Table().with_columns(
'Flavor', make_array('vanilla', 'chocolate', 'vanilla', 'chocolate'),
'Stars', make_array(5, 3, 5, 4)
)
reviews
```
| Flavor | Stars |
| --- | --- |
| vanilla | 5 |
| chocolate | 3 |
| vanilla | 5 |
| chocolate | 4 |
```py
average_review = reviews.group('Flavor', np.average)
average_review
```
| Flavor | Stars average |
| --- | --- |
| chocolate | 3.5 |
| vanilla | 5 |
我們可以連接`cones `和`average_review`,通過提供用于連接的列標簽。
```py
cones.join('Flavor', average_review, 'Flavor')
```
| Flavor | Price | Stars average |
| --- | --- | --- |
| chocolate | 6.55 | 3.5 |
| chocolate | 5.75 | 3.5 |
| vanilla | 4.75 | 5 |
注意草莓圓筒是如何消失的。 沒有草莓圓筒的評價,所以沒有草莓的行可以連接的東西。 這可能是一個問題,也可能不是 - 這取決于我們試圖使用連接表執行的分析。
## 灣區共享單車
在本章結尾,我們通過使用我們學過的所有方法,來檢驗新的大型數據集。 我們還將介紹一個強大的可視化工具`map_table`。
灣區自行車共享服務公司在其系統中發布了一個數據集,描述了 2014 年 9 月到 2015 年 8 月期間的每個自行車的租賃。 總共有 354152 次出租。 表的列是:
+ 租賃 ID
+ 租賃的時間,以秒為單位
+ 開始日期
+ 起點站的名稱和起始終端的代碼
+ 終點站的名稱和終止終端的代碼
+ 自行車的序列號
+ 訂閱者類型和郵政編碼
```py
trips = Table.read_table('trip.csv')
trips
```
| Trip ID | Duration | Start Date | Start Station | Start Terminal | End Date | End Station | End Terminal | Bike # | Subscriber Type | Zip Code |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 913460 | 765 | 8/31/2015 23:26 | Harry Bridges Plaza (Ferry Building) | 50 | 8/31/2015 23:39 | San Francisco Caltrain (Townsend at 4th) | 70 | 288 | Subscriber | 2139 |
| 913459 | 1036 | 8/31/2015 23:11 | San Antonio Shopping Center | 31 | 8/31/2015 23:28 | Mountain View City Hall | 27 | 35 | Subscriber | 95032 |
| 913455 | 307 | 8/31/2015 23:13 | Post at Kearny | 47 | 8/31/2015 23:18 | 2nd at South Park | 64 | 468 | Subscriber | 94107 |
| 913454 | 409 | 8/31/2015 23:10 | San Jose City Hall | 10 | 8/31/2015 23:17 | San Salvador at 1st | 8 | 68 | Subscriber | 95113 |
| 913453 | 789 | 8/31/2015 23:09 | Embarcadero at Folsom | 51 | 8/31/2015 23:22 | Embarcadero at Sansome | 60 | 487 | Customer | 9069 |
| 913452 | 293 | 8/31/2015 23:07 | Yerba Buena Center of the Arts (3rd @ Howard) | 68 | 8/31/2015 23:12 | San Francisco Caltrain (Townsend at 4th) | 70 | 538 | Subscriber | 94118 |
| 913451 | 896 | 8/31/2015 23:07 | Embarcadero at Folsom | 51 | 8/31/2015 23:22 | Embarcadero at Sansome | 60 | 363 | Customer | 92562 |
| 913450 | 255 | 8/31/2015 22:16 | Embarcadero at Sansome | 60 | 8/31/2015 22:20 | Steuart at Market | 74 | 470 | Subscriber | 94111 |
| 913449 | 126 | 8/31/2015 22:12 | Beale at Market | 56 | 8/31/2015 22:15 | Temporary Transbay Terminal (Howard at Beale) | 55 | 439 | Subscriber | 94130 |
| 913448 | 932 | 8/31/2015 21:57 | Post at Kearny | 47 | 8/31/2015 22:12 | South Van Ness at Market | 66 | 472 | Subscriber | 94702 |
(省略了 354142 行)
我們只專注于免費行程,這是持續不到 1800 秒(半小時)的行程。 長途行程需要付費。
下面的直方圖顯示,大部分行程需要大約 10 分鐘(600 秒)左右。 很少有人花了近 30 分鐘(1800 秒),可能是因為人們試圖在截止時間之前退還自行車,以免付費。
```py
commute = trips.where('Duration', are.below(1800))
commute.hist('Duration', unit='Second')
```

我們可以通過指定更多的桶來獲得更多的細節。 但整體形狀并沒有太大變化。
```py
commute.hist('Duration', bins=60, unit='Second')
```

### 使用`group `和`pivot`探索數據
我們可以使用`group `來識別最常用的起點站。
```py
starts = commute.group('Start Station').sort('count', descending=True)
starts
```
| Start Station | count |
| --- | --- |
| San Francisco Caltrain (Townsend at 4th) | 25858 |
| San Francisco Caltrain 2 (330 Townsend) | 21523 |
| Harry Bridges Plaza (Ferry Building) | 15543 |
| Temporary Transbay Terminal (Howard at Beale) | 14298 |
| 2nd at Townsend | 13674 |
| Townsend at 7th | 13579 |
| Steuart at Market | 13215 |
| Embarcadero at Sansome | 12842 |
| Market at 10th | 11523 |
| Market at Sansome | 11023 |
(省略了 60 行)
大多數行程起始于 Townsend 的 Caltrain 站,和舊金山的四號車站。 人們乘坐火車進入城市,然后使用共享單車到達下一個目的地。
`group`方法也可以用于按照起點站和終點站,對租賃進行分類。
```py
commute.group(['Start Station', 'End Station'])
```
| Start Station | End Station | count |
| --- | --- | --- |
| 2nd at Folsom | 2nd at Folsom | 54 |
| 2nd at Folsom | 2nd at South Park | 295 |
| 2nd at Folsom | 2nd at Townsend | 437 |
| 2nd at Folsom | 5th at Howard | 113 |
| 2nd at Folsom | Beale at Market | 127 |
| 2nd at Folsom | Broadway St at Battery St | 67 |
| 2nd at Folsom | Civic Center BART (7th at Market) | 47 |
| 2nd at Folsom | Clay at Battery | 240 |
| 2nd at Folsom | Commercial at Montgomery | 128 |
| 2nd at Folsom | Davis at Jackson | 28 |
(省略了 1619 行)
共有五十四次行程開始和結束于在 Folsom 二號車站,。 有很多人(437 人)往返于 Folsom 二號和 Townsend 二號車站之間。
`pivot`方法執行相同的分類,但將結果顯示在一個透視表中,該表顯示了起點和終點站的所有可能組合,即使其中一些不對應任何行程。 請記住,`pivot`函數的第一個參數指定了數據透視表的列標簽;第二個參數指定行標簽。
在 Beale at Market 附近有一個火車站以及一個灣區快速公交(BART)站,解釋了從那里開始和結束的大量行程。
```py
commute.pivot('Start Station', 'End Station')
```
| End Station | 2nd at Folsom | 2nd at South Park | 2nd at Townsend | 5th at Howard | Adobe on Almaden | Arena Green / SAP Center | Beale at Market | Broadway St at Battery St | California Ave Caltrain Station | Castro Street and El Camino Real | Civic Center BART (7th at Market) | Clay at Battery | Commercial at Montgomery | Cowper at University | Davis at Jackson | Embarcadero at Bryant | Embarcadero at Folsom | Embarcadero at Sansome | Embarcadero at Vallejo | Evelyn Park and Ride | Franklin at Maple | Golden Gate at Polk | Grant Avenue at Columbus Avenue | Harry Bridges Plaza (Ferry Building) | Howard at 2nd | Japantown | MLK Library | Market at 10th | Market at 4th | Market at Sansome | Mechanics Plaza (Market at Battery) | Mezes Park | Mountain View Caltrain Station | Mountain View City Hall | Palo Alto Caltrain Station | Park at Olive | Paseo de San Antonio | Post at Kearny | Powell Street BART | Powell at Post (Union Square) | Redwood City Caltrain Station | Redwood City Medical Center | Redwood City Public Library | Rengstorff Avenue / California Street | Ryland Park | SJSU - San Salvador at 9th | SJSU 4th at San Carlos | San Antonio Caltrain Station | San Antonio Shopping Center | San Francisco Caltrain (Townsend at 4th) | San Francisco Caltrain 2 (330 Townsend) | San Francisco City Hall | San Jose City Hall | San Jose Civic Center | San Jose Diridon Caltrain Station | San Mateo County Center | San Pedro Square | San Salvador at 1st | Santa Clara County Civic Center | Santa Clara at Almaden | South Van Ness at Market | Spear at Folsom | St James Park | Stanford in Redwood City | Steuart at Market | Temporary Transbay Terminal (Howard at Beale) | Townsend at 7th | University and Emerson | Washington at Kearny | Yerba Buena Center of the Arts (3rd @ Howard) |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 2nd at Folsom | 54 | 190 | 554 | 107 | 0 | 0 | 40 | 21 | 0 | 0 | 44 | 78 | 54 | 0 | 9 | 77 | 32 | 41 | 14 | 0 | 0 | 11 | 30 | 416 | 53 | 0 | 0 | 169 | 114 | 302 | 33 | 0 | 0 | 0 | 0 | 0 | 0 | 60 | 121 | 88 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 694 | 445 | 21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 38 | 57 | 0 | 0 | 39 | 237 | 342 | 0 | 17 | 31 |
| 2nd at South Park | 295 | 164 | 71 | 180 | 0 | 0 | 208 | 85 | 0 | 0 | 112 | 87 | 160 | 0 | 37 | 56 | 178 | 83 | 116 | 0 | 0 | 57 | 73 | 574 | 500 | 0 | 0 | 139 | 199 | 1633 | 119 | 0 | 0 | 0 | 0 | 0 | 0 | 299 | 84 | 113 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 559 | 480 | 48 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 66 | 152 | 0 | 0 | 374 | 429 | 143 | 0 | 63 | 209 |
| 2nd at Townsend | 437 | 151 | 185 | 92 | 0 | 0 | 608 | 350 | 0 | 0 | 80 | 329 | 168 | 0 | 386 | 361 | 658 | 506 | 254 | 0 | 0 | 27 | 315 | 2607 | 295 | 0 | 0 | 110 | 225 | 845 | 177 | 0 | 0 | 0 | 0 | 0 | 0 | 120 | 100 | 141 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 905 | 299 | 14 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 72 | 508 | 0 | 0 | 2349 | 784 | 417 | 0 | 57 | 166 |
| 5th at Howard | 113 | 177 | 148 | 83 | 0 | 0 | 59 | 130 | 0 | 0 | 203 | 76 | 129 | 0 | 30 | 57 | 49 | 166 | 54 | 0 | 0 | 85 | 78 | 371 | 478 | 0 | 0 | 303 | 158 | 168 | 90 | 0 | 0 | 0 | 0 | 0 | 0 | 93 | 183 | 169 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 690 | 1859 | 48 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 116 | 102 | 0 | 0 | 182 | 750 | 200 | 0 | 43 | 267 |
| Adobe on Almaden | 0 | 0 | 0 | 0 | 11 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 17 | 7 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 25 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 7 | 7 | 16 | 0 | 0 | 0 | 0 | 0 | 19 | 23 | 265 | 0 | 20 | 4 | 5 | 10 | 0 | 0 | 14 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Arena Green / SAP Center | 0 | 0 | 0 | 0 | 7 | 64 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 16 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 24 | 3 | 7 | 0 | 0 | 0 | 0 | 0 | 6 | 20 | 7 | 0 | 56 | 12 | 38 | 259 | 0 | 0 | 13 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Beale at Market | 127 | 79 | 183 | 59 | 0 | 0 | 59 | 661 | 0 | 0 | 201 | 75 | 101 | 0 | 247 | 178 | 38 | 590 | 165 | 0 | 0 | 54 | 435 | 57 | 72 | 0 | 0 | 286 | 236 | 163 | 26 | 0 | 0 | 0 | 0 | 0 | 0 | 49 | 227 | 179 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 640 | 269 | 25 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 243 | 128 | 0 | 0 | 16 | 167 | 35 | 0 | 64 | 45 |
| Broadway St at Battery St | 67 | 89 | 279 | 119 | 0 | 0 | 1022 | 110 | 0 | 0 | 62 | 283 | 226 | 0 | 191 | 198 | 79 | 231 | 35 | 0 | 0 | 5 | 70 | 168 | 49 | 0 | 0 | 32 | 97 | 341 | 214 | 0 | 0 | 0 | 0 | 0 | 0 | 169 | 71 | 218 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 685 | 438 | 7 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 18 | 106 | 0 | 0 | 344 | 748 | 50 | 0 | 79 | 47 |
| California Ave Caltrain Station | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 38 | 1 | 0 | 0 | 0 | 29 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 192 | 40 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 6 | 0 | 0 | 0 | 17 | 10 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 57 | 0 | 0 |
| Castro Street and El Camino Real | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 30 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 14 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 931 | 34 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 7 | 0 | 0 | 0 | 4 | 12 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
(省略了 60 行)
我們也可以使用`pivot`來尋找起點和終點站之間的最短騎行時間。 在這里,`pivot`已經接受了可選參數`Duration`,以及`min`函數,它對每個單元格中的值執行。
```py
commute.pivot('Start Station', 'End Station', 'Duration', min)
```
| End Station | 2nd at Folsom | 2nd at South Park | 2nd at Townsend | 5th at Howard | Adobe on Almaden | Arena Green / SAP Center | Beale at Market | Broadway St at Battery St | California Ave Caltrain Station | Castro Street and El Camino Real | Civic Center BART (7th at Market) | Clay at Battery | Commercial at Montgomery | Cowper at University | Davis at Jackson | Embarcadero at Bryant | Embarcadero at Folsom | Embarcadero at Sansome | Embarcadero at Vallejo | Evelyn Park and Ride | Franklin at Maple | Golden Gate at Polk | Grant Avenue at Columbus Avenue | Harry Bridges Plaza (Ferry Building) | Howard at 2nd | Japantown | MLK Library | Market at 10th | Market at 4th | Market at Sansome | Mechanics Plaza (Market at Battery) | Mezes Park | Mountain View Caltrain Station | Mountain View City Hall | Palo Alto Caltrain Station | Park at Olive | Paseo de San Antonio | Post at Kearny | Powell Street BART | Powell at Post (Union Square) | Redwood City Caltrain Station | Redwood City Medical Center | Redwood City Public Library | Rengstorff Avenue / California Street | Ryland Park | SJSU - San Salvador at 9th | SJSU 4th at San Carlos | San Antonio Caltrain Station | San Antonio Shopping Center | San Francisco Caltrain (Townsend at 4th) | San Francisco Caltrain 2 (330 Townsend) | San Francisco City Hall | San Jose City Hall | San Jose Civic Center | San Jose Diridon Caltrain Station | San Mateo County Center | San Pedro Square | San Salvador at 1st | Santa Clara County Civic Center | Santa Clara at Almaden | South Van Ness at Market | Spear at Folsom | St James Park | Stanford in Redwood City | Steuart at Market | Temporary Transbay Terminal (Howard at Beale) | Townsend at 7th | University and Emerson | Washington at Kearny | Yerba Buena Center of the Arts (3rd @ Howard) |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 2nd at Folsom | 61 | 97 | 164 | 268 | 0 | 0 | 271 | 407 | 0 | 0 | 483 | 329 | 306 | 0 | 494 | 239 | 262 | 687 | 599 | 0 | 0 | 639 | 416 | 282 | 80 | 0 | 0 | 506 | 237 | 167 | 250 | 0 | 0 | 0 | 0 | 0 | 0 | 208 | 264 | 290 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 300 | 303 | 584 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 590 | 208 | 0 | 0 | 318 | 149 | 448 | 0 | 429 | 165 |
| 2nd at South Park | 61 | 60 | 77 | 86 | 0 | 0 | 78 | 345 | 0 | 0 | 290 | 188 | 171 | 0 | 357 | 104 | 81 | 490 | 341 | 0 | 0 | 369 | 278 | 122 | 60 | 0 | 0 | 416 | 142 | 61 | 68 | 0 | 0 | 0 | 0 | 0 | 0 | 60 | 237 | 106 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 63 | 66 | 458 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 399 | 63 | 0 | 0 | 79 | 61 | 78 | 0 | 270 | 96 |
| 2nd at Townsend | 137 | 67 | 60 | 423 | 0 | 0 | 311 | 469 | 0 | 0 | 546 | 520 | 474 | 0 | 436 | 145 | 232 | 509 | 494 | 0 | 0 | 773 | 549 | 325 | 221 | 0 | 0 | 667 | 367 | 265 | 395 | 0 | 0 | 0 | 0 | 0 | 0 | 319 | 455 | 398 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 125 | 133 | 742 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 777 | 241 | 0 | 0 | 291 | 249 | 259 | 0 | 610 | 284 |
| 5th at Howard | 215 | 300 | 384 | 68 | 0 | 0 | 357 | 530 | 0 | 0 | 179 | 412 | 364 | 0 | 543 | 419 | 359 | 695 | 609 | 0 | 0 | 235 | 474 | 453 | 145 | 0 | 0 | 269 | 161 | 250 | 306 | 0 | 0 | 0 | 0 | 0 | 0 | 234 | 89 | 202 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 256 | 221 | 347 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 375 | 402 | 0 | 0 | 455 | 265 | 357 | 0 | 553 | 109 |
| Adobe on Almaden | 0 | 0 | 0 | 0 | 84 | 275 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 701 | 387 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 229 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 441 | 452 | 318 | 0 | 0 | 0 | 0 | 0 | 309 | 146 | 182 | 0 | 207 | 358 | 876 | 101 | 0 | 0 | 369 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Arena Green / SAP Center | 0 | 0 | 0 | 0 | 305 | 62 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 526 | 546 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 403 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 288 | 875 | 685 | 0 | 0 | 0 | 0 | 0 | 440 | 420 | 153 | 0 | 166 | 624 | 759 | 116 | 0 | 0 | 301 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| Beale at Market | 219 | 343 | 417 | 387 | 0 | 0 | 60 | 155 | 0 | 0 | 343 | 122 | 153 | 0 | 115 | 216 | 170 | 303 | 198 | 0 | 0 | 437 | 235 | 149 | 204 | 0 | 0 | 535 | 203 | 88 | 72 | 0 | 0 | 0 | 0 | 0 | 0 | 191 | 316 | 191 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 499 | 395 | 526 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 575 | 173 | 0 | 0 | 87 | 94 | 619 | 0 | 222 | 264 |
| Broadway St at Battery St | 351 | 424 | 499 | 555 | 0 | 0 | 195 | 62 | 0 | 0 | 520 | 90 | 129 | 0 | 70 | 340 | 284 | 128 | 101 | 0 | 0 | 961 | 148 | 168 | 357 | 0 | 0 | 652 | 351 | 218 | 221 | 0 | 0 | 0 | 0 | 0 | 0 | 255 | 376 | 316 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 611 | 599 | 799 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 738 | 336 | 0 | 0 | 169 | 291 | 885 | 0 | 134 | 411 |
| California Ave Caltrain Station | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 82 | 1645 | 0 | 0 | 0 | 628 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1771 | 0 | 484 | 131 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1077 | 0 | 0 | 0 | 870 | 911 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 531 | 0 | 0 |
| Castro Street and El Camino Real | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 74 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 499 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 201 | 108 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 654 | 0 | 0 | 0 | 953 | 696 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
(省略了 60 行)
有人從 Folsom 的二號車站騎到 Beale at Market,距離大約五個街區,非常快(271 秒,約 4.5 分鐘)。 2nd Avenue 和 Almaden 的 Adobe 之間沒有自行車行程,因為后者在不同的城市。
### 繪制地圖
`stations`表包含每個自行車站的地理信息,包括緯度,經度和“地標”,它是該站所在城市的名稱。
```py
stations = Table.read_table('station.csv')
stations
```
| station_id | name | lat | long | dockcount | landmark | installation |
| --- | --- | --- | --- | --- | --- | --- |
| 2 | San Jose Diridon Caltrain Station | 37.3297 | -121.902 | 27 | San Jose | 8/6/2013 |
| 3 | San Jose Civic Center | 37.3307 | -121.889 | 15 | San Jose | 8/5/2013 |
| 4 | Santa Clara at Almaden | 37.334 | -121.895 | 11 | San Jose | 8/6/2013 |
| 5 | Adobe on Almaden | 37.3314 | -121.893 | 19 | San Jose | 8/5/2013 |
| 6 | San Pedro Square | 37.3367 | -121.894 | 15 | San Jose | 8/7/2013 |
| 7 | Paseo de San Antonio | 37.3338 | -121.887 | 15 | San Jose | 8/7/2013 |
| 8 | San Salvador at 1st | 37.3302 | -121.886 | 15 | San Jose | 8/5/2013 |
| 9 | Japantown | 37.3487 | -121.895 | 15 | San Jose | 8/5/2013 |
| 10 | San Jose City Hall | 37.3374 | -121.887 | 15 | San Jose | 8/6/2013 |
| 11 | MLK Library | 37.3359 | -121.886 | 19 | San Jose | 8/6/2013 |
(省略了 60 行)
我們可以使用`Marker.map_table`來繪制一個車站所在位置的地圖。 該函數在一個表格上進行操作,該表格的列依次是緯度,經度以及每個點的可選標識符。
```py
Marker.map_table(stations.select('lat', 'long', 'name'))
```
[地圖 1](src/7-1.html)
地圖使用 OpenStreetMap 創建的,OpenStreetMap 是一個開放的在線地圖系統,你可以像使用 Google 地圖或任何其他在線地圖一樣使用。 放大到舊金山,看看車站如何分布。 點擊一個標記,看看它是哪個站。
你也可以用彩色圓圈表示地圖上的點。 這是舊金山自行車站的地圖。
```py
sf = stations.where('landmark', are.equal_to('San Francisco'))
sf_map_data = sf.select('lat', 'long', 'name')
Circle.map_table(sf_map_data, color='green', radius=200)
```
[地圖 2](src/7-2.html)
### 更多信息的地圖:`join`的應用
自行車站位于灣區五個不同的城市。 為了區分每個城市,通過使用不同的顏色,我們首先使用`group`來標識所有城市,并為每個城市分配一個顏色。
```py
cities = stations.group('landmark').relabeled('landmark', 'city')
cities
```
| city | count |
| --- | --- |
| Mountain View | 7 |
| Palo Alto | 5 |
| Redwood City | 7 |
| San Francisco | 35 |
| San Jose | 16 |
```py
colors = cities.with_column('color', make_array('blue', 'red', 'green', 'orange', 'purple'))
colors
```
| city | count | color |
| --- | --- | --- |
| Mountain View | 7 | blue |
| Palo Alto | 5 | red |
| Redwood City | 7 | green |
| San Francisco | 35 | orange |
| San Jose | 16 | purple |
現在我們可以按照`landmark`連接`stations`和`colors`,之后選取繪制地圖所需的列。
```py
joined = stations.join('landmark', colors, 'city')
colored = joined.select('lat', 'long', 'name', 'color')
Marker.map_table(colored)
```
[地圖 3](src/7-3.html)
現在五個不同城市由五種不同顏色標記。
要查看大部分自行車租賃的來源,讓我們確定起點站:
```py
starts = commute.group('Start Station').sort('count', descending=True)
starts
```
| Start Station | count |
| --- | --- | --- |
| San Francisco Caltrain (Townsend at 4th) | 25858 |
| San Francisco Caltrain 2 (330 Townsend) | 21523 |
| Harry Bridges Plaza (Ferry Building) | 15543 |
| Temporary Transbay Terminal (Howard at Beale) | 14298 |
| 2nd at Townsend | 13674 |
| Townsend at 7th | 13579 |
| Steuart at Market | 13215 |
| Embarcadero at Sansome | 12842 |
| Market at 10th | 11523 |
| Market at Sansome | 11023 |
(省略了 60 行)
我們可以包含映射這些車站所需的地理數據,首先連接`starts `的`stations`:
```py
station_starts = stations.join('name', starts, 'Start Station')
station_starts
```
| name | station_id | lat | long | dockcount | landmark | installation | count |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 2nd at Folsom | 62 | 37.7853 | -122.396 | 19 | San Francisco | 8/22/2013 | 7841 |
| 2nd at South Park | 64 | 37.7823 | -122.393 | 15 | San Francisco | 8/22/2013 | 9274 |
| 2nd at Townsend | 61 | 37.7805 | -122.39 | 27 | San Francisco | 8/22/2013 | 13674 |
| 5th at Howard | 57 | 37.7818 | -122.405 | 15 | San Francisco | 8/21/2013 | 7394 |
| Adobe on Almaden | 5 | 37.3314 | -121.893 | 19 | San Jose | 8/5/2013 | 522 |
| Arena Green / SAP Center | 14 | 37.3327 | -121.9 | 19 | San Jose | 8/5/2013 | 590 |
| Beale at Market | 56 | 37.7923 | -122.397 | 19 | San Francisco | 8/20/2013 | 8135 |
| Broadway St at Battery St | 82 | 37.7985 | -122.401 | 15 | San Francisco | 1/22/2014 | 7460 |
| California Ave Caltrain Station | 36 | 37.4291 | -122.143 | 15 | Palo Alto | 8/14/2013 | 300 |
| Castro Street and El Camino Real | 32 | 37.386 | -122.084 | 11 | Mountain View | 12/31/2013 | 1137 |
(省略了 58 行)
現在我們只提取繪制地圖所需的數據,為每個站添加一個顏色和一個面積。 面積是起始于每個站點的租用次數的 1000 倍,其中選擇了常數 1000,以便在地圖上以適當的比例繪制圓圈。
```py
starts_map_data = station_starts.select('lat', 'long', 'name').with_columns(
'color', 'blue',
'area', station_starts.column('count') * 1000
)
starts_map_data.show(3)
Circle.map_table(starts_map_data)
```
| lat | long | name | color | area |
| --- | --- | --- | --- | --- |
| 37.7853 | -122.396 | 2nd at Folsom | blue | 7841000 |
| 37.7823 | -122.393 | 2nd at South Park | blue | 9274000 |
| 37.7805 | -122.39 | 2nd at Townsend | blue | 13674000 |
(省略了 65 行)
[地圖 4](src/7-4.html)
舊金山的一大塊表明,這個城市的東部是灣區自行車租賃的重點區域。