Tidy datasets are all alike, but every messy dataset is messy in its own way.
Hadley Wickham
Source: https://db.cec.gov.tw/ElecTable/Election?type=President
from urllib.parse import quote
file_name = "總統-A05-4-候選人得票數一覽表-各投開票所(臺北市).xls"
file_name_url = quote(file_name)
print(file_name_url)
%E7%B8%BD%E7%B5%B1-A05-4-%E5%80%99%E9%81%B8%E4%BA%BA%E5%BE%97%E7%A5%A8%E6%95%B8%E4%B8%80%E8%A6%BD%E8%A1%A8-%E5%90%84%E6%8A%95%E9%96%8B%E7%A5%A8%E6%89%80%28%E8%87%BA%E5%8C%97%E5%B8%82%29.xls
import pandas as pd
file_path = "https://taiwan-election-data.s3-ap-northeast-1.amazonaws.com/presidential_2020/{}".format(file_name_url)
xls_df = pd.read_excel(file_path, skiprows=[0, 1, 3, 4])
xls_df.head()
Unnamed: 0 | Unnamed: 1 | Unnamed: 2 | (1)\n宋楚瑜\n余湘 | (2)\n韓國瑜\n張善政 | (3)\n蔡英文\n賴清德 | Unnamed: 6 | Unnamed: 7 | Unnamed: 8 | Unnamed: 9 | Unnamed: 10 | Unnamed: 11 | Unnamed: 12 | Unnamed: 13 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 總 計 | NaN | NaN | 70,769 | 685,830 | 875,854 | 1,632,453 | 21,381 | 1,653,834 | 143 | 1,653,977 | 513,287 | 2,167,264 | 76.3098 |
1 | 松山區 | NaN | NaN | 5,436 | 55,918 | 64,207 | 125,561 | 1,762 | 127,323 | 2 | 127,325 | 37,329 | 164,654 | 77.3276 |
2 | NaN | 莊敬里 | 573.0 | 36 | 391 | 429 | 856 | 14 | 870 | 0 | 870 | 230 | 1,100 | 79.0909 |
3 | NaN | 莊敬里 | 574.0 | 46 | 382 | 438 | 866 | 12 | 878 | 0 | 878 | 259 | 1,137 | 77.2208 |
4 | NaN | 莊敬里 | 575.0 | 48 | 393 | 389 | 830 | 22 | 852 | 0 | 852 | 262 | 1,114 | 76.4811 |
column_names = list(xls_df.columns)
n_candidates = len(column_names) - 11
candidate_numbers_names = column_names[3:(3+n_candidates)]
print(candidate_numbers_names)
['(1)\n宋楚瑜\n余湘', '(2)\n韓國瑜\n張善政', '(3)\n蔡英文\n賴清德']
from string import ascii_uppercase
column_names = ["district", "village", "office"] + candidate_numbers_names + list(ascii_uppercase[:8])
xls_df.columns = column_names
xls_df.head()
district | village | office | (1)\n宋楚瑜\n余湘 | (2)\n韓國瑜\n張善政 | (3)\n蔡英文\n賴清德 | A | B | C | D | E | F | G | H | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 總 計 | NaN | NaN | 70,769 | 685,830 | 875,854 | 1,632,453 | 21,381 | 1,653,834 | 143 | 1,653,977 | 513,287 | 2,167,264 | 76.3098 |
1 | 松山區 | NaN | NaN | 5,436 | 55,918 | 64,207 | 125,561 | 1,762 | 127,323 | 2 | 127,325 | 37,329 | 164,654 | 77.3276 |
2 | NaN | 莊敬里 | 573.0 | 36 | 391 | 429 | 856 | 14 | 870 | 0 | 870 | 230 | 1,100 | 79.0909 |
3 | NaN | 莊敬里 | 574.0 | 46 | 382 | 438 | 866 | 12 | 878 | 0 | 878 | 259 | 1,137 | 77.2208 |
4 | NaN | 莊敬里 | 575.0 | 48 | 393 | 389 | 830 | 22 | 852 | 0 | 852 | 262 | 1,114 | 76.4811 |
xls_df.head()
district | village | office | (1)\n宋楚瑜\n余湘 | (2)\n韓國瑜\n張善政 | (3)\n蔡英文\n賴清德 | A | B | C | D | E | F | G | H | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 總 計 | NaN | NaN | 70,769 | 685,830 | 875,854 | 1,632,453 | 21,381 | 1,653,834 | 143 | 1,653,977 | 513,287 | 2,167,264 | 76.3098 |
1 | 松山區 | NaN | NaN | 5,436 | 55,918 | 64,207 | 125,561 | 1,762 | 127,323 | 2 | 127,325 | 37,329 | 164,654 | 77.3276 |
2 | 松山區 | 莊敬里 | 573.0 | 36 | 391 | 429 | 856 | 14 | 870 | 0 | 870 | 230 | 1,100 | 79.0909 |
3 | 松山區 | 莊敬里 | 574.0 | 46 | 382 | 438 | 866 | 12 | 878 | 0 | 878 | 259 | 1,137 | 77.2208 |
4 | 松山區 | 莊敬里 | 575.0 | 48 | 393 | 389 | 830 | 22 | 852 | 0 | 852 | 262 | 1,114 | 76.4811 |
print(xls_df["district"].unique())
['總\u3000計' '\u3000松山區' '\u3000信義區' '\u3000大安區' '\u3000中山區' '\u3000中正區' '\u3000大同區' '\u3000萬華區' '\u3000文山區' '\u3000南港區' '\u3000內湖區' '\u3000士林區' '\u3000北投區']
print(xls_df["district"].unique())
['總計' '松山區' '信義區' '大安區' '中山區' '中正區' '大同區' '萬華區' '文山區' '南港區' '內湖區' '士林區' '北投區']
xls_df.head()
district | village | office | (1)\n宋楚瑜\n余湘 | (2)\n韓國瑜\n張善政 | (3)\n蔡英文\n賴清德 | A | B | C | D | E | F | G | H | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 松山區 | 莊敬里 | 573.0 | 36 | 391 | 429 | 856 | 14 | 870 | 0 | 870 | 230 | 1,100 | 79.0909 |
1 | 松山區 | 莊敬里 | 574.0 | 46 | 382 | 438 | 866 | 12 | 878 | 0 | 878 | 259 | 1,137 | 77.2208 |
2 | 松山區 | 莊敬里 | 575.0 | 48 | 393 | 389 | 830 | 22 | 852 | 0 | 852 | 262 | 1,114 | 76.4811 |
3 | 松山區 | 莊敬里 | 576.0 | 43 | 389 | 462 | 894 | 14 | 908 | 0 | 908 | 271 | 1,179 | 77.0144 |
4 | 松山區 | 東榮里 | 577.0 | 38 | 431 | 545 | 1,014 | 18 | 1,032 | 0 | 1,032 | 272 | 1,304 | 79.1411 |
get_tidy_data()
¶file_name = "總統-A05-4-候選人得票數一覽表-各投開票所(新北市).xls"
file_name_url = quote(file_name)
file_path = "https://taiwan-election-data.s3-ap-northeast-1.amazonaws.com/presidential_2020/{}".format(file_name_url)
get_tidy_data(file_path).head()
district | village | office | (1)\n宋楚瑜\n余湘 | (2)\n韓國瑜\n張善政 | (3)\n蔡英文\n賴清德 | A | B | C | D | E | F | G | H | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 板橋區 | 留侯里 | 1124.0 | 58 | 430 | 557 | 1,045 | 24 | 1,069 | 0 | 1,069 | 304 | 1,373 | 77.8587 |
1 | 板橋區 | 流芳里 | 1125.0 | 50 | 345 | 534 | 929 | 6 | 935 | 0 | 935 | 311 | 1,246 | 75.0401 |
2 | 板橋區 | 赤松里 | 1126.0 | 24 | 218 | 336 | 578 | 15 | 593 | 0 | 593 | 153 | 746 | 79.4906 |
3 | 板橋區 | 黃石里 | 1127.0 | 35 | 248 | 423 | 706 | 13 | 719 | 0 | 719 | 230 | 949 | 75.7640 |
4 | 板橋區 | 挹秀里 | 1128.0 | 37 | 289 | 351 | 677 | 13 | 690 | 0 | 690 | 188 | 878 | 78.5877 |
更換資料來源,就可以讀入 22 個縣市的投票所明細
admin_areas = ["臺北市", "新北市", "桃園市", "臺中市", "臺南市", "高雄市", "新竹縣", "苗栗縣", "彰化縣", "南投縣", "雲林縣", "嘉義縣", "屏東縣", "宜蘭縣", "花蓮縣", "臺東縣", "澎湖縣", "基隆市", "新竹市", "嘉義市", "金門縣", "連江縣"]
file_names = ["總統-A05-4-候選人得票數一覽表-各投開票所({}).xls".format(admin_area) for admin_area in admin_areas]
file_name_urls = [quote(file_name) for file_name in file_names]
file_paths = ["https://taiwan-election-data.s3-ap-northeast-1.amazonaws.com/presidential_2020/{}".format(file_name_url) for file_name_url in file_name_urls]
df_dict = {}
for file_path, admin_area in zip(file_paths, admin_areas):
tidy_df = get_tidy_data(file_path)
df_dict[admin_area] = tidy_df
print("現在正在處理{}的資料...".format(admin_area))
print("資料外觀為:", tidy_df.shape)
現在正在處理臺北市的資料... 資料外觀為: (1728, 14) 現在正在處理新北市的資料... 資料外觀為: (2559, 14) 現在正在處理桃園市的資料... 資料外觀為: (1242, 14) 現在正在處理臺中市的資料... 資料外觀為: (1849, 14) 現在正在處理臺南市的資料... 資料外觀為: (1507, 14) 現在正在處理高雄市的資料... 資料外觀為: (2006, 14) 現在正在處理新竹縣的資料... 資料外觀為: (445, 14) 現在正在處理苗栗縣的資料... 資料外觀為: (480, 14) 現在正在處理彰化縣的資料... 資料外觀為: (1071, 14) 現在正在處理南投縣的資料... 資料外觀為: (492, 14) 現在正在處理雲林縣的資料... 資料外觀為: (609, 14) 現在正在處理嘉義縣的資料... 資料外觀為: (530, 14) 現在正在處理屏東縣的資料... 資料外觀為: (705, 14) 現在正在處理宜蘭縣的資料... 資料外觀為: (421, 14) 現在正在處理花蓮縣的資料... 資料外觀為: (324, 14) 現在正在處理臺東縣的資料... 資料外觀為: (236, 14) 現在正在處理澎湖縣的資料... 資料外觀為: (119, 14) 現在正在處理基隆市的資料... 資料外觀為: (274, 14) 現在正在處理新竹市的資料... 資料外觀為: (344, 14) 現在正在處理嘉義市的資料... 資料外觀為: (191, 14) 現在正在處理金門縣的資料... 資料外觀為: (84, 14) 現在正在處理連江縣的資料... 資料外觀為: (10, 14)
df_dict['臺北市'].head()
district | village | office | (1)\n宋楚瑜\n余湘 | (2)\n韓國瑜\n張善政 | (3)\n蔡英文\n賴清德 | A | B | C | D | E | F | G | H | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 松山區 | 莊敬里 | 573.0 | 36 | 391 | 429 | 856 | 14 | 870 | 0 | 870 | 230 | 1,100 | 79.0909 |
1 | 松山區 | 莊敬里 | 574.0 | 46 | 382 | 438 | 866 | 12 | 878 | 0 | 878 | 259 | 1,137 | 77.2208 |
2 | 松山區 | 莊敬里 | 575.0 | 48 | 393 | 389 | 830 | 22 | 852 | 0 | 852 | 262 | 1,114 | 76.4811 |
3 | 松山區 | 莊敬里 | 576.0 | 43 | 389 | 462 | 894 | 14 | 908 | 0 | 908 | 271 | 1,179 | 77.0144 |
4 | 松山區 | 東榮里 | 577.0 | 38 | 431 | 545 | 1,014 | 18 | 1,032 | 0 | 1,032 | 272 | 1,304 | 79.1411 |
df_dict['新北市'].head()
district | village | office | (1)\n宋楚瑜\n余湘 | (2)\n韓國瑜\n張善政 | (3)\n蔡英文\n賴清德 | A | B | C | D | E | F | G | H | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 板橋區 | 留侯里 | 1124.0 | 58 | 430 | 557 | 1,045 | 24 | 1,069 | 0 | 1,069 | 304 | 1,373 | 77.8587 |
1 | 板橋區 | 流芳里 | 1125.0 | 50 | 345 | 534 | 929 | 6 | 935 | 0 | 935 | 311 | 1,246 | 75.0401 |
2 | 板橋區 | 赤松里 | 1126.0 | 24 | 218 | 336 | 578 | 15 | 593 | 0 | 593 | 153 | 746 | 79.4906 |
3 | 板橋區 | 黃石里 | 1127.0 | 35 | 248 | 423 | 706 | 13 | 719 | 0 | 719 | 230 | 949 | 75.7640 |
4 | 板橋區 | 挹秀里 | 1128.0 | 37 | 289 | 351 | 677 | 13 | 690 | 0 | 690 | 188 | 878 | 78.5877 |
使用 22 個資料框無法做「跨縣市」的分析
tpe = df_dict["臺北市"]
tpe.head()
district | village | office | (1)\n宋楚瑜\n余湘 | (2)\n韓國瑜\n張善政 | (3)\n蔡英文\n賴清德 | A | B | C | D | E | F | G | H | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 松山區 | 莊敬里 | 573.0 | 36 | 391 | 429 | 856 | 14 | 870 | 0 | 870 | 230 | 1,100 | 79.0909 |
1 | 松山區 | 莊敬里 | 574.0 | 46 | 382 | 438 | 866 | 12 | 878 | 0 | 878 | 259 | 1,137 | 77.2208 |
2 | 松山區 | 莊敬里 | 575.0 | 48 | 393 | 389 | 830 | 22 | 852 | 0 | 852 | 262 | 1,114 | 76.4811 |
3 | 松山區 | 莊敬里 | 576.0 | 43 | 389 | 462 | 894 | 14 | 908 | 0 | 908 | 271 | 1,179 | 77.0144 |
4 | 松山區 | 東榮里 | 577.0 | 38 | 431 | 545 | 1,014 | 18 | 1,032 | 0 | 1,032 | 272 | 1,304 | 79.1411 |
tpe.head()
district | village | office | (1)\n宋楚瑜\n余湘 | (2)\n韓國瑜\n張善政 | (3)\n蔡英文\n賴清德 | |
---|---|---|---|---|---|---|
0 | 松山區 | 莊敬里 | 573.0 | 36 | 391 | 429 |
1 | 松山區 | 莊敬里 | 574.0 | 46 | 382 | 438 |
2 | 松山區 | 莊敬里 | 575.0 | 48 | 393 | 389 |
3 | 松山區 | 莊敬里 | 576.0 | 43 | 389 | 462 |
4 | 松山區 | 東榮里 | 577.0 | 38 | 431 | 545 |
candidate_infos = list(tpe.columns[3:])
tpe = tpe.melt(id_vars=['district', 'village', 'office'], value_vars=candidate_infos, var_name="candidate_info", value_name='votes')
tpe.head()
district | village | office | candidate_info | votes | |
---|---|---|---|---|---|
0 | 松山區 | 莊敬里 | 573.0 | (1)\n宋楚瑜\n余湘 | 36 |
1 | 松山區 | 莊敬里 | 574.0 | (1)\n宋楚瑜\n余湘 | 46 |
2 | 松山區 | 莊敬里 | 575.0 | (1)\n宋楚瑜\n余湘 | 48 |
3 | 松山區 | 莊敬里 | 576.0 | (1)\n宋楚瑜\n余湘 | 43 |
4 | 松山區 | 東榮里 | 577.0 | (1)\n宋楚瑜\n余湘 | 38 |
print(presidential_votes.shape)
presidential_votes.head()
(51678, 6)
district | village | office | candidate_info | votes | admin_area | |
---|---|---|---|---|---|---|
0 | 松山區 | 莊敬里 | 573.0 | (1)\n宋楚瑜\n余湘 | 36 | 臺北市 |
1 | 松山區 | 莊敬里 | 574.0 | (1)\n宋楚瑜\n余湘 | 46 | 臺北市 |
2 | 松山區 | 莊敬里 | 575.0 | (1)\n宋楚瑜\n余湘 | 48 | 臺北市 |
3 | 松山區 | 莊敬里 | 576.0 | (1)\n宋楚瑜\n余湘 | 43 | 臺北市 |
4 | 松山區 | 東榮里 | 577.0 | (1)\n宋楚瑜\n余湘 | 38 | 臺北市 |
presidential_votes.tail()
district | village | office | candidate_info | votes | admin_area | |
---|---|---|---|---|---|---|
51673 | 北竿鄉 | 塘岐村、后沃村 | 6.0 | (3)\n蔡英文\n賴清德 | 121 | 連江縣 |
51674 | 北竿鄉 | 橋仔村、芹壁村、坂里村、白沙村 | 7.0 | (3)\n蔡英文\n賴清德 | 80 | 連江縣 |
51675 | 莒光鄉 | 青帆村、田沃村、西坵村 | 8.0 | (3)\n蔡英文\n賴清德 | 43 | 連江縣 |
51676 | 莒光鄉 | 福正村、大坪村 | 9.0 | (3)\n蔡英文\n賴清德 | 62 | 連江縣 |
51677 | 東引鄉 | 中柳村、樂華村 | 10.0 | (3)\n蔡英文\n賴清德 | 136 | 連江縣 |
split_candidate_info = presidential_votes["candidate_info"].str.split("\n", expand=True)
presidential_votes["number"] = split_candidate_info[0].str.replace('\(', '').str.replace('\)', '')
presidential_votes["candidates"] = split_candidate_info[1].str.cat(split_candidate_info[2], '/')
presidential_votes.head()
district | village | office | candidate_info | votes | admin_area | number | candidates | |
---|---|---|---|---|---|---|---|---|
0 | 松山區 | 莊敬里 | 573.0 | (1)\n宋楚瑜\n余湘 | 36 | 臺北市 | 1 | 宋楚瑜/余湘 |
1 | 松山區 | 莊敬里 | 574.0 | (1)\n宋楚瑜\n余湘 | 46 | 臺北市 | 1 | 宋楚瑜/余湘 |
2 | 松山區 | 莊敬里 | 575.0 | (1)\n宋楚瑜\n余湘 | 48 | 臺北市 | 1 | 宋楚瑜/余湘 |
3 | 松山區 | 莊敬里 | 576.0 | (1)\n宋楚瑜\n余湘 | 43 | 臺北市 | 1 | 宋楚瑜/余湘 |
4 | 松山區 | 東榮里 | 577.0 | (1)\n宋楚瑜\n余湘 | 38 | 臺北市 | 1 | 宋楚瑜/余湘 |
presidential_votes.head()
district | village | office | candidate_info | votes | admin_area | number | candidates | party | |
---|---|---|---|---|---|---|---|---|---|
0 | 松山區 | 莊敬里 | 573.0 | (1)\n宋楚瑜\n余湘 | 36 | 臺北市 | 1 | 宋楚瑜/余湘 | 親民黨 |
1 | 松山區 | 莊敬里 | 574.0 | (1)\n宋楚瑜\n余湘 | 46 | 臺北市 | 1 | 宋楚瑜/余湘 | 親民黨 |
2 | 松山區 | 莊敬里 | 575.0 | (1)\n宋楚瑜\n余湘 | 48 | 臺北市 | 1 | 宋楚瑜/余湘 | 親民黨 |
3 | 松山區 | 莊敬里 | 576.0 | (1)\n宋楚瑜\n余湘 | 43 | 臺北市 | 1 | 宋楚瑜/余湘 | 親民黨 |
4 | 松山區 | 東榮里 | 577.0 | (1)\n宋楚瑜\n余湘 | 38 | 臺北市 | 1 | 宋楚瑜/余湘 | 親民黨 |
["admin_area", "district", "village", "office", "number", "party", "candidates", "votes"]
¶presidential_votes.head()
admin_area | district | village | office | number | party | candidates | votes | |
---|---|---|---|---|---|---|---|---|
0 | 臺北市 | 松山區 | 莊敬里 | 573.0 | 1 | 親民黨 | 宋楚瑜/余湘 | 36 |
1 | 臺北市 | 松山區 | 莊敬里 | 574.0 | 1 | 親民黨 | 宋楚瑜/余湘 | 46 |
2 | 臺北市 | 松山區 | 莊敬里 | 575.0 | 1 | 親民黨 | 宋楚瑜/余湘 | 48 |
3 | 臺北市 | 松山區 | 莊敬里 | 576.0 | 1 | 親民黨 | 宋楚瑜/余湘 | 43 |
4 | 臺北市 | 松山區 | 東榮里 | 577.0 | 1 | 親民黨 | 宋楚瑜/余湘 | 38 |
presidential_votes['number'] = presidential_votes['number'].astype(int)
presidential_votes['office'] = presidential_votes['office'].astype(int)
presidential_votes['votes'] = presidential_votes['votes'].astype(str)
presidential_votes['votes'] = presidential_votes['votes'].str.replace(',', '').astype(int)
presidential_votes.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 51678 entries, 0 to 51677 Data columns (total 8 columns): admin_area 51678 non-null object district 51678 non-null object village 51678 non-null object office 51678 non-null int64 number 51678 non-null int64 party 51678 non-null object candidates 51678 non-null object votes 51678 non-null int64 dtypes: int64(3), object(5) memory usage: 3.2+ MB