本博文來自於 實驗樓 本博文只是記錄學習筆記,方便日后查缺補漏,如有侵權,聯系刪除
使用 Pandas 進行數據探索
介紹
本次實驗通過分析電信運營商的客戶離網率數據集來熟悉 Pandas 數據探索的常用方法,並構建一個預測客戶離網率的簡單模型。
知識點
- 排列
- 索引
- 交叉表
- 透視表
- 數據探索
Pandas 的主要方法
Pandas 是基於 NumPy 的一種工具,提供了大量數據探索的方法。Pandas 可以使用類似 SQL 的方式對 .csv、.tsv、.xlsx 等格式的數據進行處理分析。
Pandas 主要使用的數據結構是 Series 和 DataFrame 類。下面簡要介紹下這兩類:
- Series 是一種類似於一維數組的對象,它由一組數據(各種 NumPy 數據類型)及一組與之相關的數據標簽(即索引)組成。
- DataFrame 是一個二維數據結構,即一張表格,其中每列數據的類型相同。你可以把它看成由 Series 實例構成的字典。
下面開始此次實驗,我們將通過分析電信運營商的客戶離網率數據集來展示 Pandas 的主要方法。
首先載入必要的庫,即 NumPy 和 Pandas。
import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
通過 read_csv() 方法讀取數據,然后使用 head() 方法查看前 5 行數據。
df = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/1283/telecom_churn.csv')
df.head()
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | Total eve calls | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | KS | 128 | 415 | No | Yes | 25 | 265.1 | 110 | 45.07 | 197.4 | 99 | 16.78 | 244.7 | 91 | 11.01 | 10.0 | 3 | 2.70 | 1 | False |
| 1 | OH | 107 | 415 | No | Yes | 26 | 161.6 | 123 | 27.47 | 195.5 | 103 | 16.62 | 254.4 | 103 | 11.45 | 13.7 | 3 | 3.70 | 1 | False |
| 2 | NJ | 137 | 415 | No | No | 0 | 243.4 | 114 | 41.38 | 121.2 | 110 | 10.30 | 162.6 | 104 | 7.32 | 12.2 | 5 | 3.29 | 0 | False |
| 3 | OH | 84 | 408 | Yes | No | 0 | 299.4 | 71 | 50.90 | 61.9 | 88 | 5.26 | 196.9 | 89 | 8.86 | 6.6 | 7 | 1.78 | 2 | False |
| 4 | OK | 75 | 415 | Yes | No | 0 | 166.7 | 113 | 28.34 | 148.3 | 122 | 12.61 | 186.9 | 121 | 8.41 | 10.1 | 3 | 2.73 | 3 | False |
上圖中的每行對應一位客戶,每列對應客戶的一個特征。
讓我們查看一下該數據庫的維度、特征名稱和特征類型。
df.shape
(3333, 20)
上述結果表明,我們的列表包含 3333 行和 20 列。下面我們嘗試打印列名。
df.columns
Index(['State', 'Account length', 'Area code', 'International plan',
'Voice mail plan', 'Number vmail messages', 'Total day minutes',
'Total day calls', 'Total day charge', 'Total eve minutes',
'Total eve calls', 'Total eve charge', 'Total night minutes',
'Total night calls', 'Total night charge', 'Total intl minutes',
'Total intl calls', 'Total intl charge', 'Customer service calls',
'Churn'],
dtype='object')
我們還可以使用 info() 方法輸出 DataFrame 的一些總體信息。
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3333 entries, 0 to 3332
Data columns (total 20 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 State 3333 non-null object
1 Account length 3333 non-null int64
2 Area code 3333 non-null int64
3 International plan 3333 non-null object
4 Voice mail plan 3333 non-null object
5 Number vmail messages 3333 non-null int64
6 Total day minutes 3333 non-null float64
7 Total day calls 3333 non-null int64
8 Total day charge 3333 non-null float64
9 Total eve minutes 3333 non-null float64
10 Total eve calls 3333 non-null int64
11 Total eve charge 3333 non-null float64
12 Total night minutes 3333 non-null float64
13 Total night calls 3333 non-null int64
14 Total night charge 3333 non-null float64
15 Total intl minutes 3333 non-null float64
16 Total intl calls 3333 non-null int64
17 Total intl charge 3333 non-null float64
18 Customer service calls 3333 non-null int64
19 Churn 3333 non-null bool
dtypes: bool(1), float64(8), int64(8), object(3)
memory usage: 498.1+ KB
bool、int64、float64 和 object 是該數據庫特征的數據類型。這一方法同時也會顯示是否有缺失值,上述結果表明在該數據集中不存在缺失值,因為每列都包含 3333 個觀測,和我們之前使用 shape 方法得到的數字是一致的。
astype() 方法可以更改列的類型,下列公式將 Churn 離網率 特征修改為 int64 類型。
df['Churn'] = df['Churn'].astype('int64')
describe() 方法可以顯示數值特征(int64 和 float64)的基本統計學特性,如未缺失值的數值、均值、標准差、范圍、四分位數等。
df.describe()
| Account length | Area code | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | Total eve calls | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 | 3333.000000 |
| mean | 101.064806 | 437.182418 | 8.099010 | 179.775098 | 100.435644 | 30.562307 | 200.980348 | 100.114311 | 17.083540 | 200.872037 | 100.107711 | 9.039325 | 10.237294 | 4.479448 | 2.764581 | 1.562856 | 0.144914 |
| std | 39.822106 | 42.371290 | 13.688365 | 54.467389 | 20.069084 | 9.259435 | 50.713844 | 19.922625 | 4.310668 | 50.573847 | 19.568609 | 2.275873 | 2.791840 | 2.461214 | 0.753773 | 1.315491 | 0.352067 |
| min | 1.000000 | 408.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 23.200000 | 33.000000 | 1.040000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 |
| 25% | 74.000000 | 408.000000 | 0.000000 | 143.700000 | 87.000000 | 24.430000 | 166.600000 | 87.000000 | 14.160000 | 167.000000 | 87.000000 | 7.520000 | 8.500000 | 3.000000 | 2.300000 | 1.000000 | 0.000000 |
| 50% | 101.000000 | 415.000000 | 0.000000 | 179.400000 | 101.000000 | 30.500000 | 201.400000 | 100.000000 | 17.120000 | 201.200000 | 100.000000 | 9.050000 | 10.300000 | 4.000000 | 2.780000 | 1.000000 | 0.000000 |
| 75% | 127.000000 | 510.000000 | 20.000000 | 216.400000 | 114.000000 | 36.790000 | 235.300000 | 114.000000 | 20.000000 | 235.300000 | 113.000000 | 10.590000 | 12.100000 | 6.000000 | 3.270000 | 2.000000 | 0.000000 |
| max | 243.000000 | 510.000000 | 51.000000 | 350.800000 | 165.000000 | 59.640000 | 363.700000 | 170.000000 | 30.910000 | 395.000000 | 175.000000 | 17.770000 | 20.000000 | 20.000000 | 5.400000 | 9.000000 | 1.000000 |
通過 include 參數顯式指定包含的數據類型,可以查看非數值特征的統計數據。
df.describe(include=['object', 'bool'])
| State | International plan | Voice mail plan | |
|---|---|---|---|
| count | 3333 | 3333 | 3333 |
| unique | 51 | 2 | 2 |
| top | WV | No | No |
| freq | 106 | 3010 | 2411 |
value_counts() 方法可以查看類別(類型為 object )和布爾值(類型為 bool )特征。讓我們看下 Churn 離網率 的分布。
df['Churn'].value_counts()
0 2850
1 483
Name: Churn, dtype: int64
上述結果表明,在 3333 位客戶中, 2850 位是忠實客戶,他們的 Churn 值為 0。調用 value_counts() 函數時,加上 normalize=True 參數可以顯示比例。
df['Churn'].value_counts(normalize=True)
0 0.855086
1 0.144914
Name: Churn, dtype: float64
排序
DataFrame 可以根據某個變量的值(也就是列)排序。比如,根據每日消費額排序(設置 ascending=False 倒序排列)。
df.sort_values(by='Total day charge', ascending=False).head()
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | Total eve calls | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 365 | CO | 154 | 415 | No | No | 0 | 350.8 | 75 | 59.64 | 216.5 | 94 | 18.40 | 253.9 | 100 | 11.43 | 10.1 | 9 | 2.73 | 1 | 1 |
| 985 | NY | 64 | 415 | Yes | No | 0 | 346.8 | 55 | 58.96 | 249.5 | 79 | 21.21 | 275.4 | 102 | 12.39 | 13.3 | 9 | 3.59 | 1 | 1 |
| 2594 | OH | 115 | 510 | Yes | No | 0 | 345.3 | 81 | 58.70 | 203.4 | 106 | 17.29 | 217.5 | 107 | 9.79 | 11.8 | 8 | 3.19 | 1 | 1 |
| 156 | OH | 83 | 415 | No | No | 0 | 337.4 | 120 | 57.36 | 227.4 | 116 | 19.33 | 153.9 | 114 | 6.93 | 15.8 | 7 | 4.27 | 0 | 1 |
| 605 | MO | 112 | 415 | No | No | 0 | 335.5 | 77 | 57.04 | 212.5 | 109 | 18.06 | 265.0 | 132 | 11.93 | 12.7 | 8 | 3.43 | 2 | 1 |
此外,還可以根據多個列的數值排序。下面函數實現的功能為:先按 Churn 離網率 升序排列,再按 Total day charge 每日總話費 降序排列,優先級 Churn > Tatal day charge。
df.sort_values(by=['Churn', 'Total day charge'],
ascending=[True, False]).head()
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | Total eve calls | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 688 | MN | 13 | 510 | No | Yes | 21 | 315.6 | 105 | 53.65 | 208.9 | 71 | 17.76 | 260.1 | 123 | 11.70 | 12.1 | 3 | 3.27 | 3 | 0 |
| 2259 | NC | 210 | 415 | No | Yes | 31 | 313.8 | 87 | 53.35 | 147.7 | 103 | 12.55 | 192.7 | 97 | 8.67 | 10.1 | 7 | 2.73 | 3 | 0 |
| 534 | LA | 67 | 510 | No | No | 0 | 310.4 | 97 | 52.77 | 66.5 | 123 | 5.65 | 246.5 | 99 | 11.09 | 9.2 | 10 | 2.48 | 4 | 0 |
| 575 | SD | 114 | 415 | No | Yes | 36 | 309.9 | 90 | 52.68 | 200.3 | 89 | 17.03 | 183.5 | 105 | 8.26 | 14.2 | 2 | 3.83 | 1 | 0 |
| 2858 | AL | 141 | 510 | No | Yes | 28 | 308.0 | 123 | 52.36 | 247.8 | 128 | 21.06 | 152.9 | 103 | 6.88 | 7.4 | 3 | 2.00 | 1 | 0 |
索引和獲取數據
DataFrame 可以以不同的方式進行索引。
使用 DataFrame['Name'] 可以得到一個單獨的列。比如,離網率有多高?
df['Churn'].mean()
0.14491449144914492
對一家公司而言,14.5% 的離網率是一個很糟糕的數據,這么高的離網率可能導致公司破產。
布爾值索引同樣很方便,語法是 df[P(df['Name'])],P 是在檢查 Name 列每個元素時所使用的邏輯條件。這一索引的輸出是 DataFrame 的 Name 列中滿足 P 條件的行。
讓我們使用布爾值索引來回答這樣以下問題:離網用戶的數值變量的均值是多少?
df[df['Churn'] == 1].mean()
Account length 102.664596
Area code 437.817805
Number vmail messages 5.115942
Total day minutes 206.914079
Total day calls 101.335404
Total day charge 35.175921
Total eve minutes 212.410145
Total eve calls 100.561077
Total eve charge 18.054969
Total night minutes 205.231677
Total night calls 100.399586
Total night charge 9.235528
Total intl minutes 10.700000
Total intl calls 4.163561
Total intl charge 2.889545
Customer service calls 2.229814
Churn 1.000000
dtype: float64
離網用戶在白天打電話的總時長的均值是多少?
df[df['Churn'] == 1]['Total day minutes'].mean()
206.91407867494814
未使用國際套餐(International plan == NO)的忠實用戶(Churn == 0)所打的最長的國際長途是多久?
df[(df['Churn'] == 0) & (df['International plan'] == 'No')
]['Total intl minutes'].max()
18.9
DataFrame 可以通過列名、行名、行號進行索引。loc 方法為通過名稱索引,iloc 方法為通過數字索引。
通過 loc 方法輸出 0 至 5 行、State 州 至 Area code 區號 的數據。
df.loc[0:5, 'State':'Area code']
| State | Account length | Area code | |
|---|---|---|---|
| 0 | KS | 128 | 415 |
| 1 | OH | 107 | 415 |
| 2 | NJ | 137 | 415 |
| 3 | OH | 84 | 408 |
| 4 | OK | 75 | 415 |
| 5 | AL | 118 | 510 |
通過 iloc 方法輸出前 5 行的前 3 列數據(和典型的 Python 切片一樣,不含最大值)。
df.iloc[0:5, 0:3]
| State | Account length | Area code | |
|---|---|---|---|
| 0 | KS | 128 | 415 |
| 1 | OH | 107 | 415 |
| 2 | NJ | 137 | 415 |
| 3 | OH | 84 | 408 |
| 4 | OK | 75 | 415 |
df[:1] 和 df[-1:] 可以得到 DataFrame 的首行和末行。
df[-1:]
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | Total eve calls | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 3332 | TN | 74 | 415 | No | Yes | 25 | 234.4 | 113 | 39.85 | 265.9 | 82 | 22.6 | 241.4 | 77 | 10.86 | 13.7 | 4 | 3.7 | 0 | 0 |
應用函數到單元格、列、行
下面通過 apply() 方法應用函數 max 至每一列,即輸出每列的最大值。
df.apply(np.max)
State WY
Account length 243
Area code 510
International plan Yes
Voice mail plan Yes
Number vmail messages 51
Total day minutes 350.8
Total day calls 165
Total day charge 59.64
Total eve minutes 363.7
Total eve calls 170
Total eve charge 30.91
Total night minutes 395
Total night calls 175
Total night charge 17.77
Total intl minutes 20
Total intl calls 20
Total intl charge 5.4
Customer service calls 9
Churn 1
dtype: object
apply() 方法也可以應用函數至每一行,指定 axis=1 即可。在這種情況下,使用 lambda 函數十分方便。比如,下面函數選中了所有以 W 開頭的州。
df[df['State'].apply(lambda state: state[0] == 'W')].head()
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | Total eve calls | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 9 | WV | 141 | 415 | Yes | Yes | 37 | 258.6 | 84 | 43.96 | 222.0 | 111 | 18.87 | 326.4 | 97 | 14.69 | 11.2 | 5 | 3.02 | 0 | 0 |
| 26 | WY | 57 | 408 | No | Yes | 39 | 213.0 | 115 | 36.21 | 191.1 | 112 | 16.24 | 182.7 | 115 | 8.22 | 9.5 | 3 | 2.57 | 0 | 0 |
| 44 | WI | 64 | 510 | No | No | 0 | 154.0 | 67 | 26.18 | 225.8 | 118 | 19.19 | 265.3 | 86 | 11.94 | 3.5 | 3 | 0.95 | 1 | 0 |
| 49 | WY | 97 | 415 | No | Yes | 24 | 133.2 | 135 | 22.64 | 217.2 | 58 | 18.46 | 70.6 | 79 | 3.18 | 11.0 | 3 | 2.97 | 1 | 0 |
| 54 | WY | 87 | 415 | No | No | 0 | 151.0 | 83 | 25.67 | 219.7 | 116 | 18.67 | 203.9 | 127 | 9.18 | 9.7 | 3 | 2.62 | 5 | 1 |
map() 方法可以通過一個 {old_value:new_value} 形式的字典替換某一列中的值。
d = {'No': False, 'Yes': True}
df['International plan'] = df['International plan'].map(d)
df.head()
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | Total eve calls | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | KS | 128 | 415 | False | Yes | 25 | 265.1 | 110 | 45.07 | 197.4 | 99 | 16.78 | 244.7 | 91 | 11.01 | 10.0 | 3 | 2.70 | 1 | 0 |
| 1 | OH | 107 | 415 | False | Yes | 26 | 161.6 | 123 | 27.47 | 195.5 | 103 | 16.62 | 254.4 | 103 | 11.45 | 13.7 | 3 | 3.70 | 1 | 0 |
| 2 | NJ | 137 | 415 | False | No | 0 | 243.4 | 114 | 41.38 | 121.2 | 110 | 10.30 | 162.6 | 104 | 7.32 | 12.2 | 5 | 3.29 | 0 | 0 |
| 3 | OH | 84 | 408 | True | No | 0 | 299.4 | 71 | 50.90 | 61.9 | 88 | 5.26 | 196.9 | 89 | 8.86 | 6.6 | 7 | 1.78 | 2 | 0 |
| 4 | OK | 75 | 415 | True | No | 0 | 166.7 | 113 | 28.34 | 148.3 | 122 | 12.61 | 186.9 | 121 | 8.41 | 10.1 | 3 | 2.73 | 3 | 0 |
當然,使用 repalce() 方法一樣可以達到替換的目的。
df = df.replace({'Voice mail plan': d})
df.head()
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | Total eve calls | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | KS | 128 | 415 | False | True | 25 | 265.1 | 110 | 45.07 | 197.4 | 99 | 16.78 | 244.7 | 91 | 11.01 | 10.0 | 3 | 2.70 | 1 | 0 |
| 1 | OH | 107 | 415 | False | True | 26 | 161.6 | 123 | 27.47 | 195.5 | 103 | 16.62 | 254.4 | 103 | 11.45 | 13.7 | 3 | 3.70 | 1 | 0 |
| 2 | NJ | 137 | 415 | False | False | 0 | 243.4 | 114 | 41.38 | 121.2 | 110 | 10.30 | 162.6 | 104 | 7.32 | 12.2 | 5 | 3.29 | 0 | 0 |
| 3 | OH | 84 | 408 | True | False | 0 | 299.4 | 71 | 50.90 | 61.9 | 88 | 5.26 | 196.9 | 89 | 8.86 | 6.6 | 7 | 1.78 | 2 | 0 |
| 4 | OK | 75 | 415 | True | False | 0 | 166.7 | 113 | 28.34 | 148.3 | 122 | 12.61 | 186.9 | 121 | 8.41 | 10.1 | 3 | 2.73 | 3 | 0 |
分組(Groupby)
Pandas 下分組數據的一般形式為:
df.groupby(by=grouping_columns)[columns_to_show].function()
對上述函數的解釋:
groupby()方法根據 grouping_columns 的值進行分組。- 接着,選中感興趣的列(columns_to_show)。若不包括這一項,那么就會選中所有非 groupby 列(即除 grouping_colums 外的所有列)。
- 最后,應用一個或多個函數(function)。
在下面的例子中,我們根據 Churn 離網率 變量的值對數據進行分組,顯示每組的統計數據。
columns_to_show = ['Total day minutes', 'Total eve minutes',
'Total night minutes']
df.groupby(['Churn'])[columns_to_show].describe(percentiles=[])
| Total day minutes | Total eve minutes | Total night minutes | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | mean | std | min | 50% | max | count | mean | std | min | 50% | max | count | mean | std | min | 50% | max | |
| Churn | ||||||||||||||||||
| 0 | 2850.0 | 175.175754 | 50.181655 | 0.0 | 177.2 | 315.6 | 2850.0 | 199.043298 | 50.292175 | 0.0 | 199.6 | 361.8 | 2850.0 | 200.133193 | 51.105032 | 23.2 | 200.25 | 395.0 |
| 1 | 483.0 | 206.914079 | 68.997792 | 0.0 | 217.6 | 350.8 | 483.0 | 212.410145 | 51.728910 | 70.9 | 211.3 | 363.7 | 483.0 | 205.231677 | 47.132825 | 47.4 | 204.80 | 354.9 |
和上面的例子類似,只不過這次將一些函數傳給 agg(),通過 agg() 方法對分組后的數據進行聚合。
columns_to_show = ['Total day minutes', 'Total eve minutes',
'Total night minutes']
df.groupby(['Churn'])[columns_to_show].agg([np.mean, np.std, np.min, np.max])
| Total day minutes | Total eve minutes | Total night minutes | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| mean | std | amin | amax | mean | std | amin | amax | mean | std | amin | amax | |
| Churn | ||||||||||||
| 0 | 175.175754 | 50.181655 | 0.0 | 315.6 | 199.043298 | 50.292175 | 0.0 | 361.8 | 200.133193 | 51.105032 | 23.2 | 395.0 |
| 1 | 206.914079 | 68.997792 | 0.0 | 350.8 | 212.410145 | 51.728910 | 70.9 | 363.7 | 205.231677 | 47.132825 | 47.4 | 354.9 |
匯總表
Pandas 中的透視表定義如下:
透視表(Pivot Table)是電子表格程序和其他數據探索軟件中一種常見的數據匯總工具。它根據一個或多個鍵對數據進行聚合,並根據行和列上的分組將數據分配到各個矩形區域中。
通過 pivot_table() 方法可以建立透視表,其參數如下:
- values 表示需要計算的統計數據的變量列表
- index 表示分組數據的變量列表
- aggfunc 表示需要計算哪些統計數據,例如,總和、均值、最大值、最小值等。
現在,通過 pivot_table() 方法查看不同區號下白天、夜晚、深夜的電話量的均值。
df.pivot_table(['Total day calls', 'Total eve calls', 'Total night calls'],
['Area code'], aggfunc='mean')
| Total day calls | Total eve calls | Total night calls | |
|---|---|---|---|
| Area code | |||
| 408 | 100.496420 | 99.788783 | 99.039379 |
| 415 | 100.576435 | 100.503927 | 100.398187 |
| 510 | 100.097619 | 99.671429 | 100.601190 |
交叉表(Cross Tabulation)是一種用於計算分組頻率的特殊透視表,在 Pandas 中一般使用 crosstab() 方法構建交叉表。
構建一個交叉表查看樣本的 Churn 離網率 和 International plan 國際套餐 的分布情況。
pd.crosstab(df['Churn'], df['International plan'])
| International plan | False | True |
|---|---|---|
| Churn | ||
| 0 | 2664 | 186 |
| 1 | 346 | 137 |
構建一個交叉表查看 Churn 離網率 和 Voice mail plan 語音郵件套餐 的分布情況。
pd.crosstab(df['Churn'], df['Voice mail plan'], normalize=True)
| Voice mail plan | False | True |
|---|---|---|
| Churn | ||
| 0 | 0.602460 | 0.252625 |
| 1 | 0.120912 | 0.024002 |
上述結果表明,大部分用戶是忠實用戶,同時他們並不使用額外的服務(國際套餐、語音郵件)。
增減 DataFrame 的行列
在 DataFrame 中新增列有很多方法,比如,使用 insert()方法添加列,為所有用戶計算總的 Total calls 電話量。
total_calls = df['Total day calls'] + df['Total eve calls'] + \
df['Total night calls'] + df['Total intl calls']
# loc 參數是插入 Series 對象后選擇的列數
# 設置為 len(df.columns)以便將計算后的 Total calls 粘貼到最后一列
df.insert(loc=len(df.columns), column='Total calls', value=total_calls)
df.head()
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | ... | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | Total calls | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | KS | 128 | 415 | False | True | 25 | 265.1 | 110 | 45.07 | 197.4 | ... | 16.78 | 244.7 | 91 | 11.01 | 10.0 | 3 | 2.70 | 1 | 0 | 303 |
| 1 | OH | 107 | 415 | False | True | 26 | 161.6 | 123 | 27.47 | 195.5 | ... | 16.62 | 254.4 | 103 | 11.45 | 13.7 | 3 | 3.70 | 1 | 0 | 332 |
| 2 | NJ | 137 | 415 | False | False | 0 | 243.4 | 114 | 41.38 | 121.2 | ... | 10.30 | 162.6 | 104 | 7.32 | 12.2 | 5 | 3.29 | 0 | 0 | 333 |
| 3 | OH | 84 | 408 | True | False | 0 | 299.4 | 71 | 50.90 | 61.9 | ... | 5.26 | 196.9 | 89 | 8.86 | 6.6 | 7 | 1.78 | 2 | 0 | 255 |
| 4 | OK | 75 | 415 | True | False | 0 | 166.7 | 113 | 28.34 | 148.3 | ... | 12.61 | 186.9 | 121 | 8.41 | 10.1 | 3 | 2.73 | 3 | 0 | 359 |
5 rows × 21 columns
上面的代碼創建了一個中間 Series 實例,即 tatal_calls,其實可以在不創造這個實例的情況下直接添加列。
df['Total charge'] = df['Total day charge'] + df['Total eve charge'] + \
df['Total night charge'] + df['Total intl charge']
df.head()
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | ... | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | Total calls | Total charge | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | KS | 128 | 415 | False | True | 25 | 265.1 | 110 | 45.07 | 197.4 | ... | 244.7 | 91 | 11.01 | 10.0 | 3 | 2.70 | 1 | 0 | 303 | 75.56 |
| 1 | OH | 107 | 415 | False | True | 26 | 161.6 | 123 | 27.47 | 195.5 | ... | 254.4 | 103 | 11.45 | 13.7 | 3 | 3.70 | 1 | 0 | 332 | 59.24 |
| 2 | NJ | 137 | 415 | False | False | 0 | 243.4 | 114 | 41.38 | 121.2 | ... | 162.6 | 104 | 7.32 | 12.2 | 5 | 3.29 | 0 | 0 | 333 | 62.29 |
| 3 | OH | 84 | 408 | True | False | 0 | 299.4 | 71 | 50.90 | 61.9 | ... | 196.9 | 89 | 8.86 | 6.6 | 7 | 1.78 | 2 | 0 | 255 | 66.80 |
| 4 | OK | 75 | 415 | True | False | 0 | 166.7 | 113 | 28.34 | 148.3 | ... | 186.9 | 121 | 8.41 | 10.1 | 3 | 2.73 | 3 | 0 | 359 | 52.09 |
5 rows × 22 columns
使用 drop() 方法刪除列和行。
# 移除先前創捷的列
df.drop(['Total charge', 'Total calls'], axis=1, inplace=True)
# 刪除行
df.drop([1, 2]).head()
| State | Account length | Area code | International plan | Voice mail plan | Number vmail messages | Total day minutes | Total day calls | Total day charge | Total eve minutes | Total eve calls | Total eve charge | Total night minutes | Total night calls | Total night charge | Total intl minutes | Total intl calls | Total intl charge | Customer service calls | Churn | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | KS | 128 | 415 | False | True | 25 | 265.1 | 110 | 45.07 | 197.4 | 99 | 16.78 | 244.7 | 91 | 11.01 | 10.0 | 3 | 2.70 | 1 | 0 |
| 3 | OH | 84 | 408 | True | False | 0 | 299.4 | 71 | 50.90 | 61.9 | 88 | 5.26 | 196.9 | 89 | 8.86 | 6.6 | 7 | 1.78 | 2 | 0 |
| 4 | OK | 75 | 415 | True | False | 0 | 166.7 | 113 | 28.34 | 148.3 | 122 | 12.61 | 186.9 | 121 | 8.41 | 10.1 | 3 | 2.73 | 3 | 0 |
| 5 | AL | 118 | 510 | True | False | 0 | 223.4 | 98 | 37.98 | 220.6 | 101 | 18.75 | 203.9 | 118 | 9.18 | 6.3 | 6 | 1.70 | 0 | 0 |
| 6 | MA | 121 | 510 | False | True | 24 | 218.2 | 88 | 37.09 | 348.5 | 108 | 29.62 | 212.6 | 118 | 9.57 | 7.5 | 7 | 2.03 | 3 | 0 |
對上述代碼的部分解釋:
- 將相應的索引
['Total charge', 'Total calls']和axis參數(1 表示刪除列,0 表示刪除行,默認值為 0)傳給drop。 inplace參數表示是否修改原始 DataFrame (False 表示不修改現有 DataFrame,返回一個新 DataFrame,True 表示修改當前 DataFrame)。
預測離網率
首先,通過上面介紹的 crosstab() 方法構建一個交叉表來查看 International plan 國際套餐 變量和 Churn 離網率 的相關性,同時使用 countplot() 方法構建計數直方圖來可視化結果。
# 加載模塊,配置繪圖
import matplotlib.pyplot as plt
import seaborn as sns
sns.countplot(x='International plan', hue='Churn', data=df)
<matplotlib.axes._subplots.AxesSubplot at 0x21d4b580340>

上圖表明,開通了國際套餐的用戶的離網率要高很多,這是一個很有趣的觀測結果。也許,國際電話高昂的話費讓客戶很不滿意。
同理,查看 Customer service calls 客服呼叫 變量與 Chunrn 離網率 的相關性,並可視化結果。
pd.crosstab(df['Churn'], df['Customer service calls'], margins=True)
| Customer service calls | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | All |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Churn | |||||||||||
| 0 | 605 | 1059 | 672 | 385 | 90 | 26 | 8 | 4 | 1 | 0 | 2850 |
| 1 | 92 | 122 | 87 | 44 | 76 | 40 | 14 | 5 | 1 | 2 | 483 |
| All | 697 | 1181 | 759 | 429 | 166 | 66 | 22 | 9 | 2 | 2 | 3333 |
sns.countplot(x='Customer service calls', hue='Churn', data=df)
<matplotlib.axes._subplots.AxesSubplot at 0x21d4b6311c0>

上圖表明,在客服呼叫 4 次之后,客戶的離網率顯著提升。
為了更好的突出 Customer service call 客服呼叫 和 Churn 離網率 的關系,可以給 DataFrame 添加一個二元屬性 Many_service_calls,即客戶呼叫超過 3 次(Customer service calls > 3)。看下它與離網率的相關性,並可視化結果。
df['Many_service_calls'] = (df['Customer service calls'] > 3).astype('int')
pd.crosstab(df['Many_service_calls'], df['Churn'], margins=True)
| Churn | 0 | 1 | All |
|---|---|---|---|
| Many_service_calls | |||
| 0 | 2721 | 345 | 3066 |
| 1 | 129 | 138 | 267 |
| All | 2850 | 483 | 3333 |
sns.countplot(x='Many_service_calls', hue='Churn', data=df)
<matplotlib.axes._subplots.AxesSubplot at 0x21d4b5f9160>

現在我們可以創建另一張交叉表,將 Churn 離網率 與 International plan 國際套餐 及新創建的 Many_service_calls 多次客服呼叫 關聯起來。
pd.crosstab(df['Many_service_calls'] & df['International plan'], df['Churn'])
| Churn | 0 | 1 |
|---|---|---|
| row_0 | ||
| False | 2841 | 464 |
| True | 9 | 19 |
上表表明,在客服呼叫次數超過 3 次並且已辦理 International Plan 國際套餐 的情況下,預測一名客戶不忠誠的准確率(Accuracy)可以達到 85.8%,計算公式如下:
其中,TP 表示將 True 預測為 True 的數量,TN 表示將 Flase 預測為 Flase 的數量,FP 表示將 Flase 預測為 True 的數量,FN 表示將 True 預測為 Flase 的數量。
復習一下本次實驗的內容:
- 樣本中忠實客戶的份額為 85.5%。這意味着最簡單的預測「忠實客戶」的模型有 85.5% 的概率猜對。也就是說,后續模型的准確率(Accuracy)不應該比這個數字少,並且很有希望顯著高於這個數字。
- 基於一個簡單的「(客服呼叫次數 > 3) & (國際套餐 = True) => Churn = 1, else Churn = 0」規則的預測模型,可以得到 85.8% 的准確率。以后我們將討論決策樹,看看如何僅僅基於輸入數據自動找出類似的規則,而不需要我們手工設定。我們沒有應用機器學習方法就得到了兩個准確率(85.5% 和 85.8%),它們可作為后續其他模型的基線。如果經過大量的努力,我們僅將准確率提高了 0.5%,那么我們努力的方向可能出現了偏差,因為僅僅使用一個包含兩個限制規則的簡單模型就已提升了 0.3% 的准確率。
- 在訓練復雜模型之前,建議預處理一下數據,繪制一些圖表,做一些簡單的假設。此外,在實際任務上應用機器學習時,通常從簡單的方案開始,接着嘗試更復雜的方案。
相關鏈接
