風玫瑰圖就是極坐標系下的堆積分組柱形圖, 這裡定義1個函數,返回繪圖對象和刻度坐標及標籤。使用時,提取刻度坐標和標籤就行了,繪圖對象使用_進行列印。
import numpy as npfrom palettable.colorbrewer.colorbrewer import get_map
def plot_ross(ax, df, theta_col, rad_col, group_col, palette=None, theta_order=None, group_order=None, colors=None, width=0.9, startangle=0, maxangle=360, clockwise=True, **kw):if theta_order is None: theta_order = df[theta_col].drop_duplicates().sort_values().to_list()if group_order is None: group_order = df[group_col].drop_duplicates().sort_values().to_list()if (palette is None) and (colors is None): # colors = [None] * len(group_order)if (palette is not None) and (colors is None): # 只設置顏色板 colors = get_map(palette, "qualitative", len(group_order)).hex_colors startangle = startangle/180*np.pi endangle = maxangle/180*np.pi width = endangle/len(theta_order) * width theta = (np.linspace(0, endangle, len(theta_order), endpoint=False) + width/2).tolist() n = 0; b = 0for group in group_order: rad = (df.loc[df[group_col] == group] .set_index(theta_col) .reindex(theta_order, axis=0)[rad_col] .fillna(0) ) ax.bar(theta, rad, width=width, bottom=b, color=colors[n], label=str(group), **kw) n += 1 b = rad + b ax.set_theta_direction((-1 if clockwise else 1)) # 設置旋轉方向 ax.set_theta_offset(startangle) # 設置旋轉起點return (ax, (theta + [2*np.pi], theta_order + [None])) # 返回繪圖對象及刻度
# 繪圖from matplotlib import tickerimport matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['SimHei'] # 設定默認字體為SimHei以解決中文顯示亂碼問題fig = plt.figure(figsize=(5, 6), dpi=100) # 設置解析度和尺寸大小ax = fig.add_subplot(111, projection="polar")_, (theta, theta_label) = plot_ross(ax, data, theta_col="季度", rad_col="熱度", group_col="系列", palette="Set2", theta_order=["第一季度", "第二季度", "第三季度", "第四季度"], startangle=90, maxangle=270)ax.legend(ncol=1, bbox_to_anchor=(0.45, 0.55), loc='lower right')ax.set_xticks(theta) # 刻度位置ax.set_xticklabels(theta_label, color="red") # 刻度標籤ax.yaxis.set_major_formatter(ticker.NullFormatter())# ax.grid(False, axis="y", which="major") # 隱藏網格線ax.set_title("風玫瑰圖");圓環圖圓環圖也是堆積柱形圖的極坐標系變種
import numpy as npfrom palettable.colorbrewer.colorbrewer import get_map
def plot_circles(ax, df, theta_col, rad_col, group_col, palette=None, rad_order=None, group_order=None, colors=None, height=0.8, startangle=0, maxangle=360, clockwise=True, **kw):if rad_order is None: rad_order = df[rad_col].drop_duplicates().sort_values().to_list()if group_order is None: group_order = df[group_col].drop_duplicates().sort_values().to_list()if (palette is None) and (colors is None): # colors = [None] * len(group_order)if (palette is not None) and (colors is None): # 只設置顏色板 colors = get_map(palette, "qualitative", len(group_order)).hex_colors startangle = startangle/180*np.pi endangle = maxangle/180*np.pi rad = np.arange(len(rad_order)) + height/2 theta_sum = (df.groupby(rad_col)[theta_col] .sum() .reindex(rad_order) .fillna(0) ) n = 0; b = 0for group in group_order: theta = (df.loc[df[group_col] == group] .set_index(rad_col) .reindex(rad_order, axis=0)[theta_col] .fillna(0)/theta_sum * endangle ) ax.barh(rad, theta, height=height, left=b, color=colors[n], label=str(group), **kw) n += 1 b = theta + b ax.set_theta_direction((-1 if clockwise else 1)) # 設置旋轉方向 ax.set_theta_offset(startangle) # 設置旋轉起點return (ax, (rad, rad_order)) # 返回繪圖對象及刻度
# 繪圖from matplotlib import tickerimport matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['SimHei'] # 設定默認字體為SimHei以解決中文顯示亂碼問題fig = plt.figure(figsize=(5, 6), dpi=100) # 設置解析度和尺寸大小ax = fig.add_subplot(111, projection="polar")_, (rad, rad_label) = plot_circles(ax, data, theta_col="熱度", rad_col="季度", group_col="系列", palette="Set2", rad_order=["第一季度", "第二季度", "第三季度", "第四季度"], startangle=90, maxangle=270)ax.legend(ncol=1, bbox_to_anchor=(-0.01, 0.5), loc='lower left')ax.set_yticks(rad)ax.set_yticklabels(rad_label, x=-np.pi/8, # 通過x逆時針旋轉刻度標籤 ha="right", va="center", color="red") ax.set_rmin(-3) # 空心ax.xaxis.set_major_formatter(ticker.NullFormatter())ax.grid(False, axis="y", which="major") # 隱藏網格線ax.set_title("圓環圖");雷達圖使用plot()方法繪製雷達邊線,fill()方法繪製多邊形區域。編造極坐標系下首尾相連的坐標點。
# 定義1個函數,繪製1個雷達import numpy as npdef plot_radar(ax, theta_var, rad_var, color, label=None, alpha=0.5, linewidth=1, linestyle="solid", isfill=True, **kw): theta = np.linspace(0, 2*np.pi, len(theta_var), endpoint=False).tolist() + [0] rad = rad_var + [rad_var[0]]if isfill: # 是否填充 ax.plot(theta, rad, color=color, linewidth=linewidth) ax.fill(theta, rad, color=color, alpha=0.5, label=label, **kw)else: ax.plot(theta, rad, color=color, label=label, linewidth=linewidth, linestyle=linestyle)return ax
# 編造數據factors = ["財產", "年齡", "外貌", "學歷", "性格"]persons = {"張三": [7, 5, 8, 7.5, 8.5],"小王": [6, 9, 9, 6.5, 4],"小李": [7, 8, 5, 9, 5],"小強": [9, 5, 4, 6, 8],}
from palettable.colorbrewer.colorbrewer import get_mapcolors = get_map("Set2", "qualitative", len(persons)).hex_colorsimport matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['SimHei'] # 設定默認字體為SimHei以解決中文顯示亂碼問題fig, ax = plt.subplots(figsize=(6,7), subplot_kw=dict(polar=True))n = 0 # 循環繪製每個雷達for key in persons: plot_radar(ax, theta_var=factors, rad_var=persons[key], color=colors[n], label=str(key)) n += 1ax.legend(ncol=1, bbox_to_anchor=(1, 1), loc='upper left')ax.set_theta_direction(-1) # 設置旋轉方向ax.set_theta_offset(np.pi/4) # 設置旋轉起點ax.set_xticks(np.linspace(0, 2*np.pi, len(factors), endpoint=False)) # 刻度位置ax.set_xticklabels(factors, color="red", fontsize=15) # 刻度標籤ax.set_title("雷達圖", fontsize=20);轉盤圖轉盤圖很簡單,就是一圈一圈疊加餅圖對象
# 編造數據labels1 = ['中國', '美國', '日本', '俄羅斯']values1 = [200, 180, 150, 100]labels2 = [ ['北京', '上海', '廣州'], ['紐約', '舊金山', '華盛頓', '洛杉磯'], ['東京', '大阪', '名古屋'], ['莫斯科', '聖彼得堡', '伏爾加格勒']]values2 = [ [80, 60, 60], # 從大到小排序 [100, 40, 20, 20], [100, 40, 10], [60, 20, 20]]
# 計算顏色palettes = ["Blues", "RdPu", "Greens", "Oranges"] # 與labels1對應from palettable.colorbrewer.colorbrewer import get_mapcolors1 = []; colors2 = []for n in range(len(palettes)): colors = get_map(palettes[n], "sequential", len(labels2[n]), reverse=False).hex_colors colors1.append(colors[-1]) colors2.append(colors)def flatten(x): A = []; [A.extend(_) for _ in x]return A
# 繪圖import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['SimHei'] # 設定默認字體為SimHei以解決中文顯示亂碼問題fig, ax = plt.subplots(figsize=(8, 8))# 繪製外層餅圖ax.pie(x=values1, labels=labels1, explode=[0.01]*len(labels1), colors=colors1, startangle=90, textprops={'fontsize': 20}, wedgeprops={'alpha': 0.8, 'width': 0.2} )# 內層餅圖ax.pie(x=flatten(values2), labels=flatten(labels2), explode=[0.01]*len(flatten(labels2)), colors=flatten(colors2), labeldistance=0.75, startangle=90, radius=0.79, # 空心 textprops={'va': 'center', 'ha': 'center'}, rotatelabels=True, wedgeprops={'alpha': 0.8, 'width': 0.4} )fig.set_facecolor('white');山巒圖使用fill_between()方法即可繪製單個填充區域,這裡定義1個函數繪製山巒圖,可以自動扣除基線, 手動進行縱坐標基線偏移。
import numpy as npdef plot_ridges(ax, X, Y1, buttoms, labels, colors, **kw):if np.array(X).ndim == 1: X = [X]*len(Y1) # X可以與Y形狀相同,也可以是1個序列 n = 0for y in Y1: y = np.array(y) - np.min(np.array(y)) # 扣除基線 ax.fill_between(X[n], y + buttoms[n], buttoms[n], where=y>0, color=colors[n], label=labels[n], **kw) ax.plot(X[n], y + buttoms[n], color=colors[n]) n += 1return ax
# 編造數據import numpy as npfrom scipy import statsX = np.arange(-8, 8, 0.1) # 既可以是1個序列,也可以多個序列組成的列表# 多個序列組成的列表Y = [np.apply_along_axis(lambda x: stats.norm(-4,1).pdf(x), 0, X)*2, np.apply_along_axis(lambda x: stats.norm(-2,1).pdf(x), 0, X)*2, np.apply_along_axis(lambda x: stats.norm(0,1).pdf(x), 0, X)*2, np.apply_along_axis(lambda x: stats.norm(2,1).pdf(x), 0, X)*2, np.apply_along_axis(lambda x: stats.norm(4,1).pdf(x), 0, X)*2]
# 設置顏色from palettable.colorbrewer.colorbrewer import get_mapcolors = get_map("Set2", "qualitative", len(Y), reverse=False).hex_colorsbuttoms = [0, 1, 2, 3, 4] # 設置基線偏移,長度與Y一致labels = ["y1", "y2", "y3", "y4", "y5"] # 圖例標籤,長度與Y一致
# 繪圖from matplotlib import tickerimport matplotlib.pyplot as pltplt.rcParams['axes.unicode_minus'] = False # 解決圖像負號'-'不正常顯示的問題fig, ax = plt.subplots(figsize=(10, 8))plot_ridges(ax, X, Y, buttoms, labels, colors)ax.legend(loc="upper left", title=" I am legend")ax.yaxis.set_major_locator(ticker.NullLocator()) # 隱藏刻度ax.set_title("Ridges Chart", fontdict={'fontsize': 15});氣泡餅圖氣泡餅圖中的每一個餅圖的每一個扇形區域,都是由scatter()方法繪製,這涉及到很多循環, 為了方便使用,筆者定義了一個函數,繪製1個餅圖。
import matplotlib.pyplot as pltimport numpy as np
# 定義1個函數,繪製一個餅圖def draw_pie(dist, xpos, ypos, colors, size, clockwise=True, startangle=90, scale="fixed", ax=None):assert len(dist)==len(colors), 'length of `dist` must be equal to length of `colors` 'assert clockwise in [True, False], '`clockwise` must be one of [True, False]'assert scale in ["fixed", "linear", "log"], '`scale` must be one of ["fixed", "linear", "log"]'
if ax is None: fig, ax = plt.subplots(figsize=(10,8))# 計算扇形弧度範圍if clockwise: # 順時針 pie = [startangle/180*np.pi] + (-np.cumsum(dist)/np.sum(dist)*np.pi*2 + startangle/180*np.pi).tolist()else: # 逆時針 pie = [startangle/180*np.pi] + (np.cumsum(dist)/np.sum(dist)*np.pi*2 + startangle/180*np.pi).tolist() pie = list(zip(pie[:-1], pie[1:]))# 合併弧度範圍與顏色值 pie = [[colors[i], pie[i]] for i in range(len(colors))]# 循環繪製每一個扇形區域for color, r_range in pie: angles = np.linspace(r_range[0], r_range[1]) # 構造圓弧網格 x = np.insert(np.cos(angles), 0, 0, 0) # 笛卡爾坐標系下橫坐標 y = np.insert(np.sin(angles), 0, 0, 0) # 笛卡爾坐標系下縱坐標 xy = np.column_stack([x, y]) # 列合併為2維數組if scale=="fixed": ax.scatter([xpos], [ypos], marker=xy, s=size, c=color)if scale=="linear": # 餅圖尺寸線性變換 ax.scatter([xpos], [ypos], marker=xy, s=size*np.sum(dist), c=color)if scale=="log": # 對數變換 ax.scatter([xpos], [ypos], marker=xy, s=size*np.log(np.sum(dist)+1), c=color) # +1避免負數return aximport pandas as pd# 編造數據df1 = pd.DataFrame( # 扇形對應欄位不能為空 {"年份": [2011, 2012, 2013, 2014, 2015],"業績": [12, 11, 12, 11, 12],"板塊1": [1, 2, 3, 4, 5], "板塊2": [3, 3, 2, 2, 2], "板塊3": [1, 1.5, 2.5, 4, 6], "板塊4": [4, 2, 4, 4, 8], })df1labels = ["板塊1", "板塊2", "板塊3", "板塊4"] # 圖例標籤# 設置顏色from palettable.colorbrewer.colorbrewer import get_mapcolors = get_map("Set2", "qualitative", len(labels)).hex_colorsA = df1[labels].to_numpy()
plt.rcParams['font.sans-serif'] = ['SimHei'] # 設定默認字體為SimHei以解決中文顯示亂碼fig, ax = plt.subplots(figsize=(10,8))handles=[plt.bar([0],[0], color=i)[0] for i in colors] # 設置圖例
# 1行數據,1個餅圖for i in range(df1.shape[0]): draw_pie(dist=A[i], # xpos=df1["年份"][i], ypos=df1["業績"][i], # 位置數據 colors=colors, # 顏色序列 size=10000, # 固定尺寸 ax=ax)# 手動圖例ax.legend(handles=handles, # 圖例圖形 labels=labels, # 圖形對應文本標籤 loc="lower right", # 圖例位置 handlelength=2, # 箱體寬度 fontsize=14) # 圖例標籤尺寸ax.set_title('Scatter Pie Chart ', fontdict={'fontsize': 15})ax.set_xlim(2010, 2016)ax.set_ylim(10, 13);調節餅圖尺寸映射序列到尺寸:
size參數還可以映射到序列,不過需要乘1個縮放因子。labels = ["板塊1", "板塊2", "板塊3", "板塊4"] # 圖例標籤# 設置顏色from palettable.colorbrewer.colorbrewer import get_mapcolors = get_map("Set2", "qualitative", len(labels)).hex_colorsA = df1[labels].to_numpy()
plt.rcParams['font.sans-serif'] = ['SimHei'] # 設定默認字體為SimHei以解決中文顯示亂碼fig, ax = plt.subplots(figsize=(10,8))handles=[plt.bar([0],[0], color=i)[0] for i in colors] # 設置圖例
# 1行數據,1個餅圖for i in range(df1.shape[0]): draw_pie(dist=A[i], # xpos=df1["年份"][i], ypos=df1["業績"][i], # 位置數據 colors=colors, # 顏色序列 size=df1["業績"][i]*1000, # 映射序列到尺寸 clockwise=False, # 逆時針順序 startangle=0,# 扇形0刻度開始 ax=ax)# 手動圖例ax.legend(handles=handles, # 圖例圖形 labels=labels, # 圖形對應文本標籤 loc="lower right", # 圖例位置 handlelength=2, # 箱體寬度 fontsize=14) # 圖例標籤尺寸ax.set_title('Scatter Pie Chart ', fontdict={'fontsize': 15})ax.set_xlim(2010, 2016)ax.set_ylim(10, 13);尺寸線性變化:
通過參數scale可以設置餅圖尺寸隨著餅圖扇形區域對應數字和進行變換的方式。labels = ["板塊1", "板塊2", "板塊3", "板塊4"] from palettable.colorbrewer.colorbrewer import get_mapcolors = get_map("Set2", "qualitative", len(labels)).hex_colorsA = df1[labels].to_numpy()
plt.rcParams['font.sans-serif'] = ['SimHei'] fig, ax = plt.subplots(figsize=(10,8))handles=[plt.bar([0],[0], color=i)[0] for i in colors]
for i in range(df1.shape[0]): draw_pie(dist=A[i], xpos=df1["年份"][i], ypos=df1["業績"][i], colors=colors, size=1000, scale="linear", ax=ax)ax.legend(handles=handles, labels=labels, loc="lower right", handlelength=2, fontsize=14) ax.set_title('Scatter Pie Chart ', fontdict={'fontsize': 15})ax.set_xlim(2010, 2016)ax.set_ylim(10, 13);尺寸對數變化:
labels = ["板塊1", "板塊2", "板塊3", "板塊4"] # 圖例標籤# 設置顏色from palettable.colorbrewer.colorbrewer import get_mapcolors = get_map("Set2", "qualitative", len(labels)).hex_colorsA = df1[labels].to_numpy()
plt.rcParams['font.sans-serif'] = ['SimHei'] # 設定默認字體為SimHei以解決中文顯示亂碼fig, ax = plt.subplots(figsize=(10,8))handles=[plt.bar([0],[0], color=i)[0] for i in colors] # 設置圖例
# 1行數據,1個餅圖for i in range(df1.shape[0]): draw_pie(dist=A[i], # xpos=df1["年份"][i], ypos=df1["業績"][i], # 位置數據 colors=colors, # 顏色序列 size=5000, scale="log",# 對數變換 ax=ax)# 手動圖例ax.legend(handles=handles, # 圖例圖形 labels=labels, # 圖形對應文本標籤 loc="lower right", # 圖例位置 handlelength=2, # 箱體寬度 fontsize=14) # 圖例標籤尺寸ax.set_title('Scatter Pie Chart ', fontdict={'fontsize': 15})ax.set_xlim(2010, 2016)ax.set_ylim(10, 13);