2024-05-30|閱讀時間 ‧ 約 35 分鐘

如何用Python繪製堆疊直條圖(以2022年直轄市市長選舉政治獻金為例)

首先,這是我們的資料

raw-image

共有5份檔案,每份檔案的內容大致如下

所以我們要先把我們所需的資料彙整到同一個檔案中

import pandas as pd
import os

#建立所在之資料夾路徑
path = r"D:\Desktop\@PROJECT\Python\111年直轄市市長選舉政治獻金會計報告書\incomes"

#建立資料夾內所有檔案名稱的list
data_name_list = os.listdir(path)

#建立存放所有檔案的list
data_file_list = []

#利用迴圈將所有檔案存入data_file_list
for i in  data_name_list:
    pure_name = i.split(".")[0]
    globals()[f"data_{pure_name}"] = pd.read_csv(f"{path}/{i}")
    data_file_list.append(globals()[f"data_{pure_name}"])

#將data_file_list中的檔案,彙整成一個檔案(data_all)
data_all = pd.concat(data_file_list,ignore_index=True)

接著我們要整理我們想要的資訊

#利用pvt_table計算各候選人,各收支科目的總金額
data_pvt =  pd.pivot_table(data=data_all,
                           index=["擬參選人/政黨","選舉名稱","收支科目"],
                           values="收入金額",
                           aggfunc="sum").reset_index()

#更改欄位名稱
data_pvt.rename(columns={"擬參選人/政黨":"參選人"},inplace=True)

#擷取出參選市成一欄
data_pvt["參選市"] = list(map(lambda x: x[4:7] ,data_pvt["選舉名稱"]))

總共有6種收支科目,我們希望繪圖的表中有每個候選人在這幾項收支科目的金額(就算金額為0),以利我們作圖,所以:

#先建一個空list
data_file_list = []

#接著我們要建立一個包含所有參選人X收支科目,且每個金額皆為0的表
for i in data_pvt["參選人"].unique():
    globals()[f"data_in_loop_{i}"] = pd.DataFrame()
    globals()[f"data_in_loop_{i}"]["參選人"] = [i]*6
    globals()[f"data_in_loop_{i}"]["收支科目"] = data_pvt["收支科目"].unique().tolist()
    globals()[f"data_in_loop_{i}"]["金額0"] = [0]*6
    data_file_list.append(globals()[f"data_in_loop_{i}"])

data_temp = pd.concat(data_file_list)
#將兩個表做outer join合併成一個合併表(data_merge)
data_merge = pd.merge(left = data_pvt[["參選人","收支科目","收入金額"]],
                      right = data_temp,
                      how = "outer",
                      on = ["參選人","收支科目"]
                      )

data_merge["收入金額"].fillna(0,inplace=True)

接著我們還需要每個候選人的參選市的資料,所以:

#接著建立包含參選人與參選市的表(data_candidate_city)
data_candidate_city = data_pvt[["參選人","參選市"]].drop_duplicates()
#將參選市的資料串入合併表(data_merge)
data_merge = pd.merge(left = data_merge[["參選人","收支科目","收入金額"]],
                      right = data_candidate_city,
                      how = "left",
                      on = "參選人")

data_merge.sort_values(by=["參選市","參選人","收支科目"],inplace=True)

data_merge.reset_index(drop=True,inplace=True)

接著我們還需要每個候選人的收入總額的資料,所以:

#接著建立包含參選人與收入總額的表(data_candidate_city)
data_sum = pd.pivot_table(data=data_merge,index="參選人",values="收入金額",aggfunc="sum").rename(columns={"收入金額":"收入總額"}).reset_index()

#將收入總額的資料串入合併表(data_merge)
data_merge = pd.merge(left=data_merge,right=data_sum,on="參選人",how="left")

#篩選收入總額大於一千萬的候選人
data_merge = data_merge[data_merge["收入總額"]>=10000000]

以上就是我們要拿來繪圖的資料,繪圖方法如下:

import matplotlib

#設定中文字型
matplotlib.rc("font",family="Microsoft Yahei")

from matplotlib import pyplot as plt

#設定圖表大小
plt.figure(figsize=(18,12))

#以每一個參選市作為一個子圖
for i,p in zip(data_merge["參選市"].unique(),range(0,6)):

    data_city = data_merge[data_merge["參選市"] == i]

    #設定子圖位置
    globals()[f"ax_{i}"] = plt.subplot2grid((1,5),(0,p))

    #設定x值位置(每次重跑新的參選市時,x值便從0開始)
    x_value = 0

    #建立該參選市的參選人list
    xticklabels_list = data_city["參選人"].unique().tolist()

    #在各子圖內,為每個參選人繪製一根bar
    for j in data_city["參選人"].unique():

        data_city_person = data_city[data_city["參選人"] == j]

        #設定bar的起始位置(每次重跑參選人時,bar的高度起始位置便從0開始)
        y_bottom = 0

        #建立colorlist
        colorlist = ["lightcoral","royalblue","springgreen","lightgray","gold","orchid"]

        #在每個參選人內,為每個收支科目畫上不同的bar
        for k,c in zip(data_city_person["收支科目"].unique(),colorlist):

            data_city_person_item = data_city_person[data_city_person["收支科目"] == k]

            data_city_person_item.reset_index(drop=True,inplace=True)

            #設定參選人的bar的x位置順序的參數
            person_position = xticklabels_list.index(j)

            #設定該參選市有多少參選人的參數
            how_many_person = len(xticklabels_list)

            #建立x值資料
            #如果是該市的第一個參選人的話:
            if person_position == 0 :
                #且如果該市有2個參選人,那x軸起始位置設定為4/3
                if how_many_person == 2:
                    ax_x = 4/3
​                #且如果該市有3個參選人,那x軸起始位置設定為4/4
                elif how_many_person == 3:
                    ax_x = 4/4
            #其他狀況:
            else: ax_x = x_value

            #建立y值資料(每個收支科目bar的長度)
            ax_y = data_city_person_item["收入金額"][0]

            #繪製各收支科目的bar
            globals()[f"ax_{i}"].bar(ax_x,
                                     ax_y,
                                     bottom = y_bottom,
                                     width = 0.5,
                                     color = c,
                                     label = k)

            #疊代收支科目bar的起始位置
            y_bottom = y_bottom + ax_y

            #設定X軸標籤的位置
            #如果該市有2個參選人
            if how_many_person == 2:
                globals()[f"ax_{i}"].set_xticks([4/3,(4/3)*2])
            #如果該市有3個參選人
            elif how_many_person == 3:
                globals()[f"ax_{i}"].set_xticks([4/4,(4/4)*2,(4/4)*3])  

            #設定X軸標籤(參選人名稱)
            globals()[f"ax_{i}"].set_xticklabels(xticklabels_list)
            #設定X軸最大最小值
            globals()[f"ax_{i}"].set_xlim(0,4)

            #設定Y軸最大最小值
            globals()[f"ax_{i}"].set_ylim(0,120000000)

            #如果是第一張子圖的話,設定Y軸標籤位置與標籤如下
            if p == 0 :    
                globals()[f"ax_{i}"].set_yticks([20000000,40000000,60000000,80000000,100000000,120000000])
                globals()[f"ax_{i}"].set_yticklabels(["2千萬","4千萬","6千萬","8千萬","1億","1.2億"])
            #如果是其他張子圖的話,設定Y軸標籤位置與標籤為空值
            else :    
                globals()[f"ax_{i}"].set_yticks([])
                globals()[f"ax_{i}"].set_yticklabels([])

        #設定下一張子圖的X軸的值
        #如果該市有2個參選人
        if how_many_person == 2:
            #x軸的值增加4/3
            x_value = ax_x + 4/3
        #如果該市有3個參選人
        elif how_many_person == 3:
            #x軸的值增加4/4
            x_value = ax_x + 4/4

        #如果是第一章子圖中的第一位參選者,設定圖例如下:
        if (p == 0) & (person_position == 0) :
            globals()[f"ax_{i}"].legend(bbox_to_anchor=(4.8,-0.05),ncols=6)

        #設定各子圖的標題
        globals()[f"ax_{i}"].set_title(f"{i}")

plt.tight_layout()

#設定總標題
plt.suptitle("111年直轄市市長選舉政治獻金")




分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.