)
做數(shù)據分析這一行箱線圖是逃不掉的基礎圖。我最早用它的時候matplotlib 默認畫出來的箱體就是個白色帶黑邊的矩形放在報告里總覺得單調尤其是要對比三五個業(yè)務分組時光靠位置和形狀去區(qū)分眼睛看久了容易花。后來開始研究怎么給箱線圖自定義填充顏色才發(fā)現(xiàn)這里面的門道比我預想的多默認情況下 matplotlib 根本沒打算讓你直接填色需要先打開一個叫patch_artist的開關再配合boxprops參數(shù)才能控制箱體的facecolor。如果你已經在用 Python 做數(shù)據分析和可視化想畫一張既專業(yè)又有辨識度的彩色箱線圖這篇文章就是圍繞這個目標展開的。我會從箱線圖的基本原理講起再用可復現(xiàn)的代碼一步步演示如何實現(xiàn)自定義填充顏色、按分組上色、結合 seaborn 快速配色最后把我在實際項目中踩過的坑和排查方法一并整理出來。1. 箱線圖到底在展示什么想給箱線圖換顏色先得搞清楚你畫的這個圖是什么。箱線圖不是簡單的“一個盒子加兩根須”它把一組連續(xù)型數(shù)值數(shù)據的分布情況濃縮成了幾個關鍵統(tǒng)計量。理解這些統(tǒng)計量你才能在上色之后依然讀出圖表里的信息。1.1 五個統(tǒng)計量構成一份標準的箱線圖會呈現(xiàn)五個關鍵位置通常稱為五數(shù)概括。箱體上邊緣和下邊緣分別是上四分位數(shù)Q3和下四分位數(shù)Q1箱體中間那條線是中位數(shù)Q2從箱體延伸出去的兩條須線則指向非異常范圍內的最大值和最小值須線之外單獨畫的點就是被識別為離群值的數(shù)據點。我用一個具體數(shù)字舉例。假設某門店一周的日銷售額是 80、85、88、90、92、95、200 這 7 個數(shù)據排序后中位數(shù)是 90Q1 是 85Q3 是 95IQR 等于 10。按照常見的 1.5 倍 IQR 規(guī)則下邊界是 85 - 15 70上邊界是 95 15 110那么 200 就會被標成異常點須線最右端只畫到 95。箱體的高度也就是 Q1 到 Q3 這段區(qū)間集中了中間 50% 的數(shù)據這比單純報一個平均值更能反映數(shù)據是否穩(wěn)定。實際工作中我經常用箱線圖做三件事第一快速判斷一組數(shù)據是左偏還是右偏第二比較不同分組之間的中位數(shù)和離散程度第三找出異常值并跟進原因。顏色本身不改變這些統(tǒng)計結果但合理的配色能讓圖表更快被讀懂尤其是當你要在匯報時同時展示七八個分組時。1.2 為什么需要給箱線上色很多人一開始只畫單組箱線圖用默認顏色沒問題。但一旦涉及多組對比比如不同地區(qū)、不同產品線、不同月份光靠黑白線條就很難一眼看出哪個組的波動更大。給箱體填充不同的顏色本質上是把“分組”這個額外維度用視覺通道表達出來觀眾不用花時間對比每根須線的位置直接看顏色就能鎖定關注對象。還有個容易被忽視的使用場景品牌一致性。給外部客戶做交付報告時如果整份報告的主色是某一種品牌藍那么圖表卻還是 matplotlib 默認的白底黑邊視覺上會非常割裂。自定義填充顏色后箱體顏色能和報告主視覺保持一致整體專業(yè)感立刻提升。另外顏色還能用于突出“異常組”。我在一次質量分析中把合格率偏低的產線標成紅色其余產線用灰色匯報時所有人的注意力第一時間就集中到了紅色箱體上。當然使用顏色也要克制如果每組的顏色都特別鮮艷圖就會變得刺眼信息層次反而被削弱。后面我會專門講配色經驗這里先明確一個結論上色不是隨便挑幾個好看的顏色而是為了讓數(shù)據對比更清晰、報告更統(tǒng)一、重點更突出。2. 環(huán)境準備與數(shù)據樣例開始畫圖之前先把運行環(huán)境準備好。這里我用的是最常見的 Python 數(shù)據科學組合matplotlib 負責繪圖numpy 負責生成模擬數(shù)據seaborn 作為可選的美化工具。如果你用的是 Anaconda 或者 VS Code 配好了 Python 環(huán)境下面這幾行命令基本不會遇到問題。2.1 環(huán)境準備推薦使用 Python 3.8 以上版本matplotlib 用 3.5 以上版本因為低版本在boxplot的參數(shù)細節(jié)上有些小差異比如某些返回對象結構不一樣。安裝很簡單pip install matplotlib numpy seaborn如果網絡環(huán)境比較慢也可以指定國內鏡像源安裝。裝好之后在代碼開頭統(tǒng)一導入import matplotlib.pyplot as plt import numpy as np import seaborn as snsseaborn 這個庫其實不是必須的它是在 matplotlib 之上做了一層封裝讓我能用更少的代碼畫出樣式更精致的圖。但你如果只為了實現(xiàn)“自定義填充顏色”只用 matplotlib 也可以后面我會兩種方式都演示。2.2 構造演示數(shù)據直接拿真實數(shù)據不方便復現(xiàn)所以我用 numpy 生成三組模擬數(shù)據模擬三個門店的日銷售額情況。為了每次運行結果一致我設置了隨機種子。rng np.random.default_rng(42) store_a rng.normal(loc90, scale8, size120) store_b rng.normal(loc85, scale15, size120) store_c rng.normal(loc95, scale5, size120) # 手動加入幾個離群點模擬真實業(yè)務中的突發(fā)情況 store_b np.append(store_b, [150, 160]) store_c np.append(store_c, [60]) data [store_a, store_b, store_c] labels [門店A, 門店B, 門店C]這三組數(shù)據的設計思路是門店A 平均水平中等、波動適中門店B 平均水平略低但波動很大還帶兩個高值異常點門店C 平均水平最高、最穩(wěn)定但我也加了一個偏低異常值。這樣畫出來之后每組箱體之間能明顯看出差異顏色對比的效果也更直觀。3. 用 matplotlib 實現(xiàn)自定義填充顏色matplotlib 的boxplot函數(shù)是繪制箱線圖的核心入口。這一節(jié)我會從最樸素的畫法開始一步步把顏色加進去同時解釋每個參數(shù)背后的原理。3.1 最基礎的箱線圖先畫一個完全默認的箱線圖看看 matplotlib 默認給了我們什么。plt.figure(figsize(8, 6)) bp plt.boxplot(data, tick_labelslabels, patch_artistFalse) plt.title(默認樣式的箱線圖) plt.ylabel(銷售額) plt.grid(axisy, linestyle--, alpha0.6) plt.show()運行這段代碼你會看到三個白底黑邊的箱體。很多人第一次看到patch_artistFalse會很疑惑為什么默認是 False這就涉及 matplotlib 的內部繪圖機制了。在默認情況下boxplot里的箱體是用線條對象Line2D畫出來的它只有邊框顏色沒有真正意義上的填充區(qū)域所以無論你怎么設置facecolor都不會生效。只有把patch_artist改成True箱體才會被創(chuàng)建成可填充的Patch對象這時候你才有資格去設置填充顏色。我最初就是沒搞懂這一點直接傳了boxprops{facecolor: red}結果運行完什么變化都沒有還以為是自己參數(shù)寫錯了。后來翻源碼才發(fā)現(xiàn)patch_artist這個開關是前提條件。你記住一句話就行不給patch_artistTrue一切facecolor都是白給。3.2 用 patch_artist 打開填充開關把patch_artist打開之后箱體從線條對象變成了可填充圖形對象boxprops里的facecolor就能真正控制箱體內部的顏色了。最簡單的寫法如下plt.figure(figsize(8, 6)) bp plt.boxplot( data, tick_labelslabels, patch_artistTrue, # 關鍵開關 boxpropsdict( facecolor#4C72B0, # 箱體填充顏色 edgecolorblack, # 邊框顏色 linewidth1.5 ) ) plt.grid(axisy, linestyle--, alpha0.6) plt.show()這次三個箱體都變成了藍色邊框是黑色。這里顏色我用了十六進制字符串#4C72B0它是 seaborn 常用配色中的一種藍色偏沉穩(wěn)適合商業(yè)場景。你也可以用內置顏色名稱比如red、steelblue、skyblue或者 RGB 三元組(0.3, 0.45, 0.69)。有一點要提醒如果你在同一個boxprops里既設置了facecolor又設置了colorcolor會覆蓋邊框、須線等多個元素的顏色有時候會出現(xiàn)邊框也跟著變藍的情況。所以更穩(wěn)妥的做法是分開控制facecolor管填充edgecolor管邊框。3.3 boxprops 參數(shù)詳解想要畫出好看且信息完整的箱線圖只調facecolor是不夠的。boxprops作為一個字典可以接收大量線條和填充相關屬性我按實際使用頻率整理了一張表格參數(shù)作用常用取值示例facecolor箱體內部填充色#4C72B0,(0.2, 0.4, 0.6)edgecolor箱體邊框顏色black,#333333linewidth邊框線粗細1.2,2linestyle邊框線型-,--alpha整體透明度0 到 10.6,0.85hatch填充紋理打印時防顏色丟失/,o,xxzorder圖層層級控制誰蓋住誰2,3hatch這個參數(shù)很多人不知道但我強烈建議你做打印材料時考慮它。因為有些打印機默認是黑白模式或者對方會把報告打印出來看這時候僅靠顏色區(qū)分分組就沒用了。加上hatch/后箱體內部會有斜線紋理黑白打印也能清晰看出是不同分組。透明度alpha也是一個需要謹慎設置的參數(shù)。理論上透明色能減輕視覺壓迫感但如果alpha太低比如 0.2箱體顏色會變得很淺疊加在格子背景上可能看不太清。我自己的習慣是商業(yè)正式圖表降到 0.7 左右既保留顏色質感又不至于太刺眼。4. 按分組給不同箱體上色上面演示的是所有箱體統(tǒng)一用一種顏色。但實際分析中更多情況是每個分組需要各自的顏色這樣可以在同一張圖里清晰地對照多個門店、多個時段。這一節(jié)我會介紹兩種方式手動指定顏色列表以及用 colormap 自動映射。4.1 手動指定多組顏色當分組數(shù)量不多時3 到 6 組手動指定顏色是最直接可控的。核心思路是先把boxplot返回的結果保存下來然后遍歷每個箱體逐個設置set_facecolor。boxplot的返回值是一個字典里面的boxes鍵對應所有箱體對象的列表。colors [#4C72B0, #DD8452, #55A868] plt.figure(figsize(8, 6)) bp plt.boxplot( data, tick_labelslabels, patch_artistTrue, medianpropsdict(colorblack, linewidth2) ) for patch, color in zip(bp[boxes], colors): patch.set_facecolor(color) patch.set_edgecolor(#333333) patch.set_linewidth(1.2) plt.title(每個分組使用不同填充顏色) plt.ylabel(銷售額) plt.grid(axisy, linestyle--, alpha0.6) plt.show()這里我用zip把箱體對象和顏色列表一一對應順序不會錯。為什么不用索引for i in range(len(bp[boxes]))因為一旦你后面增刪了分組數(shù)據索引寫法很容易越界或者錯位而zip會安全地按最短列表停止至少不會報錯這個習慣我在很多循環(huán)場景里都保持下來了。顏色列表的選擇上要注意組與組之間的亮度差。比如三組分別用深藍、橙、綠對比度足夠但如果你選了淺藍、淺灰、淺粉它們之間的界限會比較模糊圖例一多就分不清了。4.2 使用 colormap 自動映射如果分組數(shù)量很多比如 8 個、10 個甚至更多手動寫顏色列表就很痛苦也容易讓顏色之間缺乏遞進感。這時候可以用 matplotlib 自帶的 colormap 來生成一組顏色。colormap 是一系列連續(xù)變化的顏色常見的有viridis、plasma、coolwarm等。import matplotlib.colors as mcolors n_groups len(data) cmap plt.get_cmap(viridis) norm mcolors.Normalize(vmin0, vmaxn_groups - 1) colors [cmap(norm(i)) for i in range(n_groups)] plt.figure(figsize(8, 6)) bp plt.boxplot( data, tick_labelslabels, patch_artistTrue, medianpropsdict(colorwhite, linewidth2) ) for patch, color in zip(bp[boxes], colors): patch.set_facecolor(color) patch.set_edgecolor(#333333) plt.title(使用 viridis 自動映射顏色) plt.ylabel(銷售額) plt.grid(axisy, linestyle--, alpha0.6) plt.show()這段代碼的思路是先用Normalize把組序號 0 到 2 映射到 0 到 1 的范圍再用cmap(norm(i))取出對應顏色。這樣就算你有 20 組數(shù)據也不需要手寫 20 個顏色值。用 colormap 時有個細節(jié)viridis這類感知均勻的色圖顏色從深紫到明黃過渡雖然好看但它偏向表達“從小到大”的數(shù)值等級。如果你的分組本身沒有大小順序只是并列關系用連續(xù)的 colormap 反而可能讓讀者誤以為組 A 比組 C 數(shù)值更大。遇到這種并列分組我建議還是回到 4.1 的離散顏色方案或者選擇色相跨度大一些的定性色圖比如tab10。4.3 同時自定義中位數(shù)線、異常點顏色箱線圖的信息不只是箱體中位數(shù)線、異常點、須線都在傳遞信息。給箱體上色以后千萬注意線條顏色和箱體填充色之間的關系。如果中位數(shù)線和箱體顏色同為深色中位數(shù)可能就看不清了整個箱體的高度信息會丟失。我常用的做法是把中位數(shù)線設置成與箱體對比明顯的顏色。顏色較淺的箱體配黑色中位數(shù)線顏色較深的箱體配白色中位數(shù)線。同時異常點一般用醒目的顏色標出因為異常值往往是業(yè)務上需要重點關注的數(shù)據。下面這段代碼是一次性調整所有元素的示例plt.figure(figsize(9, 6)) bp plt.boxplot( data, tick_labelslabels, patch_artistTrue, boxpropsdict(facecolor#4C72B0, edgecolor#2A3F5F, linewidth1.2), medianpropsdict(colorwhite, linewidth2.5), whiskerpropsdict(color#2A3F5F, linewidth1.5, linestyle-), cappropsdict(color#2A3F5F, linewidth1.5), flierpropsdict(markero, markerfacecolor#C44E52, markeredgecolor#C44E52, markersize6) ) plt.grid(axisy, linestyle--, alpha0.6) plt.show()各參數(shù)的作用如下medianprops中位數(shù)線的樣式我建議加大線寬讓中位數(shù)位置更突出。whiskerprops須線的樣式通常和箱體邊框保持一致顏色。capprops須線末端的橫線用來標記非異常范圍內的最小值與最大值。flierprops異常點的樣式包括點形狀、填充色、邊框色、尺寸。這一套設置下來圖表的層次就非常清楚了白色中位數(shù)線在藍色箱體上很明顯紅色異常點不會被誤認為正常數(shù)據。顏色之間的對比度不僅僅是審美問題更是信息傳遞問題。5. 用 seaborn 一行實現(xiàn)帶填充色的箱線圖如果你不想花太多時間調 matplotlib 的參數(shù)seaborn 是一個很好的替代方案。它上層封裝了boxplot對輸入數(shù)據的格式更友好尤其是當你手上已經是一個 DataFrame 時一行代碼就能畫出帶填充顏色的箱線圖。5.1 palette 參數(shù)介紹假設數(shù)據已經整理成了長格式的 DataFrame包含一個分組列和一個數(shù)值列。下面構造一份import pandas as pd df pd.DataFrame({ 門店: np.repeat(labels, 120), 銷售額: np.concatenate([store_a[:120], store_b[:120], store_c[:120]]) }) sns.boxplot(datadf, x門店, y銷售額, paletteSet2) plt.title(seaborn 默認 palette 配色箱線圖) plt.show()palette參數(shù)可以直接傳字符串比如Set2、husl、pastel也可以傳一個顏色列表順序對應分組順序最靈活的是傳一個字典鍵是分組名稱值是顏色。color_map { 門店A: #4C72B0, 門店B: #DD8452, 門店C: #55A868 } sns.boxplot(datadf, x門店, y銷售額, palettecolor_map) plt.title(seaborn 使用顏色字典精確控制每個分組) plt.show()用字典最大的好處是即使 DataFrame 里分組的順序發(fā)生變化顏色和分組之間的綁定關系也不會錯亂。這在數(shù)據源字段順序不穩(wěn)定時非常有用。seaborn 默認的箱體邊框是白色這在深色背景圖或者白色背景上都很干凈但如果你覺得白色邊框太輕可以在boxplot中加上linewidth2讓邊框更明顯。5.2 與 matplotlib 混排時的回填seaborn 雖然方便但它返回的是一個 matplotlib 的Axes對象所以你在它之后依然可以用 plt 系列函數(shù)去微調標題、坐標軸、網格線。也可以利用返回的Axes拿到花了幾分鐘才調好的圖形細節(jié)。有一個常見需求是先用 seaborn 畫箱線圖再往同一張圖上疊加其他統(tǒng)計信息比如把每個組的均值點畫上去。代碼可以這樣寫ax sns.boxplot(datadf, x門店, y銷售額, paletteSet2) means df.groupby(門店)[銷售額].mean() for idx, (store, mean_val) in enumerate(means.items()): ax.scatter(idx, mean_val, markerD, s60, colorblack, zorder3) plt.title(箱線圖上疊加均值點) plt.show()這里的核心思路是seaborn 和 matplotlib 本來就共用底層對象不需要在兩種繪圖風格之間做切換你可以把ax.scatter看成是對同一個坐標畫布追加元素。我個人偏愛這種“先用 seaborn 快速出圖再用 matplotlib 精細微調”的組合方式效率高也保留了足夠的靈活性。6. 常見問題與排查實錄這部分是實打實的經驗復盤。我見過身邊不少同事在給箱線圖上色時踩坑有些問題排查起來很費時間但其實原因就那么幾個。我按常見程度從高到低列出來并附上排查思路。6.1 設置了 facecolor 卻不變色這是最經典的問題我在前面已經提到過。現(xiàn)象是boxprops{facecolor: red}寫進去了圖還是白底的。原因幾乎都是沒有設置patch_artistTrue。排查步驟很簡單第一確認boxplot調用里有沒有patch_artistTrue第二確認顏色是寫在boxprops里而不是直接寫在boxplot的最外層第三檢查顏色值本身是否合法比如十六進制字符串有沒有寫錯RGB 三元組是否在 0 到 1 范圍內。如果你用bp[boxes][0].get_facecolor()拿到的是白色或透明色的 RGBA 值那說明問題還是出在patch_artist上。這個函數(shù)是我調試時很常用的“體檢工具”。6.2 填充色蓋住了中位數(shù)線設置成深色填充后中位數(shù)線也設置成深色比如黑色結果中位數(shù)線和箱體邊緣融為一體看不出中位數(shù)在哪。嚴格說不是蓋住而是對比度不夠。解決方式有兩種一是把中位數(shù)線改成白色或淺色線寬加大到 2.5這適合深色箱體二是降低箱體填充色的透明度比如facecolor(0.2, 0.4, 0.6, 0.5)讓深色背景上的深色線條透出來。前一種方式更干凈我推薦優(yōu)先調整medianprops。還有一個容易被忽略的地方當你用patch.set_facecolor(color)循環(huán)上色時medianprops必須在boxplot初始化時就傳好因為中位數(shù)線的對象已經生成之后你依然可以通過bp[medians]來改但直接在初始化時設置最省事。6.3 顏色列表與分組數(shù)量不匹配手動傳顏色列表時最常見的問題是顏色數(shù)量少于分組數(shù)量。用zip(bp[boxes], colors)時不會報錯但最后幾組箱體會保持默認顏色或上次循環(huán)留下的顏色很容易看不出問題。如果顏色數(shù)量多于分組數(shù)量多出來的顏色靜靜躺在列表里沒人用同樣不報錯。這兩種情況都屬于“災難性失敗的很安靜”的類型讓人防不勝防。我的排查習慣是在對每個箱體上色后馬上用print(len(bp[boxes]), len(colors))確認兩邊數(shù)量一致。另外盡量不要在圖例里手寫顏色而是從修改后的圖形對象中提取真實的顏色來生成圖例句柄這樣圖例和實際圖形永遠對得上。6.4 保存圖片后顏色發(fā)灰有時候屏幕顯示顏色很鮮艷保存成 PNG 后卻發(fā)灰變暗問題通常出在配色方案上。某些顏色在純白背景下顯示正常但系統(tǒng)會自動進行色彩管理加上字體渲染寬度不同整體觀感會變化。這類問題我一般從兩個角度解決。第一保存時提高分辨率用dpi200或 300能讓顏色邊緣更平滑整體畫面更細膩。第二如果想保持透明背景用plt.savefig(boxplot.png, transparentTrue, dpi300)但要注意透明背景經過某些看圖軟件展示時會反白慎用于正式交付場景。保存矢量圖用plt.savefig(boxplot.svg)顏色精度極高放到 PPT 或設計軟件里也很穩(wěn)。還有一個容易被忽略的問題是屏幕色域差異。同一份紅色在你的顯示器上看著發(fā)暗在別人筆記本上可能偏亮。所以正式報告里我通常會在顏色旁邊加文本標簽或圖例文字保證即使色彩有輕微偏差分組信息也不會丟。7. 幾個實際項目里的配色經驗技術細節(jié)聊完了最后分享一些我真實項目里積累的配色原則。這些經驗不一定出現(xiàn)在官方文檔里但對最終成圖質量影響很大。先看“品牌色優(yōu)先”。如果這份圖要進公司匯報 PPT我建議直接從公司 VI 規(guī)范里取主色和輔助色而不是臨時去調色板里選。這樣整份報告的顏色系統(tǒng)是統(tǒng)一的領導看完不會覺得圖表是“外包風格”。具體操作時我會把品牌色整理成顏色列表專門放在一個配置模塊里所有項目統(tǒng)一調用。再說“顏色數(shù)量控制”。一張箱線圖里顏色數(shù)量我盡量控制在 3 到 6 個。超過 7 個時即使顏色有差異人眼也很難快速區(qū)分不如改用其他視覺通道比如箱體填充紋理、邊框線型。分組特別多時我更傾向于拆成小圖或者先用序號排序再用顏色強調其中一組?!邦伾姓Z義”。如果你在畫的是業(yè)務監(jiān)控數(shù)據盡量讓顏色本身包含含義紅色代表異常綠色代表正常藍色代表中性。這樣讀者不需要看圖例單憑生活常識就能快速判斷。比如我在分析門店銷售額時把異常波動的門店用紅色箱體標出其余用灰色信息傳達效率非常高。還要記得“打印與色盲友好”。前面提到的hatch參數(shù)我在做紙質匯報材料時一定會加因為黑白打印無法顯示色彩區(qū)別。對于色盲人群紅綠搭配是最容易被混淆的所以我會改用藍橙搭配或者同時用紋理區(qū)分。一次給客戶做交付對方明確指出小組內部有紅綠色盲同事從那以后我的默認配色方案就從紅綠改成了藍橙。最后一個建議給箱線圖加圖例時不要直接拿顏色名稱當分組名稱。我通常會把圖例和顏色關系用一個小表格放在圖表下方或者直接在箱體上方標注文字。顏色作為視覺輔助非常好用但只有配合清晰的文字說明圖表才能真正自解釋。我個人在實際操作中的體會是自定義填充顏色的價值不在于炫技而在于讓數(shù)據圖表更快地說明問題。當你面對一張多組對比的箱線圖顏色能讓觀眾在 10 秒內鎖定哪組波動最大、哪組中位數(shù)最高這就是這項技能最大的回報。下次你再畫箱線圖不妨先把默認的白色箱體換成有語義的顏色再看一眼圖表是否立刻變得更容易讀了。