(資料科學學習手札79)基於geopandas的空間資料分析——深入淺出分層設色
阿新 • • 發佈:2020-03-09
> 本文對應程式碼和資料已上傳至我的`Github`倉庫[https://github.com/CNFeffery/DataScienceStudyNotes](https://github.com/CNFeffery/DataScienceStudyNotes)
# 1 簡介
通過前面的文章,我們已經對`geopandas`中的**資料結構**、**座標參考系**、**檔案IO**以及**基礎視覺化**有了較為深入的學習,其中在**基礎視覺化**那篇文章中我們提到了分層設色地圖,可以對與多邊形關聯的數值屬性進行分層,並分別對映不同的填充顏色,但只是開了個頭舉了個簡單的例子,實際資料視覺化過程中的分層設色有一套策略方法。
作為*基於geopandas的空間資料分析*系列文章的第五篇,通過本文你將會學習到基於`geopandas`和機器學習的**分層設色**。
# 2 基於geopandas的分層設色
**地區分佈圖**(*Choropleth maps*,又叫面量圖)作為可能是最常見的一種地理視覺化方法,其核心是對某個與向量面關聯的數值序列進行有意義的分層,併為這些分層選擇合適美觀的色彩,最後完成對地圖的著色,優點是美觀且直觀,即使對地理資訊一竅不通的人,也能通過顏色區分出不同面之間的同質性與異質性:
圖1
但同樣地,如果對資料分層採取的方法有失嚴謹沒有很好的遵循資料特點,會很容易讓看到圖的人產生出不正確的判斷,下面我們按照先分層,後設色的順序進行介紹。
## 2.1 基於mapclassify的資料分層
上一篇文章中我們提到過,,在`geopandas.GeoDataFrame.plot()`中,引數`scheme`對應的資料分層是基於第三方庫`mapclassify`實現的,因此要想對`geopandas`中的資料分層有深入的瞭解,我們就得先來了解一下`mapclassify`中的各種資料分層演算法,用到的資料是系列文章前幾期使用地滾瓜爛熟的新冠肺炎疫情資料,資料處理過程同上一篇文章,這裡不再解釋:
圖2
### 2.1.1 BoxPlot
`BoxPlot`即箱線圖,是統計學中使用到的一種方法:對個數為$n$觀測資料從小到大進行排序,分別得到位置處於$0.25n$、$0.5n$以及$0.75n$的觀測值,稱為$Q_{1}$、$Median$以及$Q_{3}$(即第一四位數、中位數和第三四分位數),並定義$Q_{3}-Q_{1}$為$IQR$,以$Q_{1}-1.5IQR$為下限,以$Q3+1.5IQR$為上限,將小於下限或大於上限的觀測值作為離群異常值,最後用影象的形式表達上述計算結果,如圖2的上圖,而圖2的下圖對應著概率估計,可以看出,箱線圖法實際上是基於概率估計的一種異常值剔除方法,因為離群值只有$0.0035*2=0.007$的概率會出現,即如果你想要找出資料中的異常高低值,`BoxPlot`是不錯的選擇:
圖3
在`mapclassify`中我們使用`BoxPlot()`來為資料實現箱線圖分層:
```Python
import mapclassify as mc
# 對各省2020-03-08對應的累計確診數量進行分層
bp = mc.BoxPlot(temp['province_confirmedCount'])
# 檢視資料分層結果
bp
```
圖4
可以看出通過箱線圖法將資料分成了五類,其中**異常值**只有1個即為湖北省,下面我們配合`geopandas`來對上述結果進行視覺化,和上一篇文章一樣,按照省級單位名稱連線我們的疫情資料與向量資料:
圖5
接著對其進行視覺化,在上一篇文章圖28的基礎上,將`scheme`引數改為`BoxPlot`,又因為箱線圖可以看作無監督問題,故分層數量`k`在這裡無效,刪去:
```Python
fig, ax = plt.subplots(figsize=(10, 10))
ax = data_with_geometry.to_crs(albers_proj).plot(ax=ax,
column='province_confirmedCount',
cmap='Reds',
missing_kwds={
"color": "lightgrey",
"edgecolor": "black",
"hatch": "////",
"label": "缺失值"
},
legend=True,
scheme='BoxPlot',
legend_kwds={
'loc': 'lower left',
'title': '確診數量分級',
'shadow': True
})
ax = nine_lines.geometry.to_crs(albers_proj).plot(ax=ax,
edgecolor='grey',
linewidth=3,
alpha=0.4)
ax.axis('off')
plt.suptitle('新型冠狀肺炎累計確診數量地區分佈', fontsize=24) # 新增最高級別標題
plt.tight_layout(pad=4.5) # 調整不同標題之間間距
ax.text(-2800000, 1300000, '* 原始資料來源:丁香園,\n其中臺灣及香港資料缺失') # 新增資料說明
fig.savefig('圖6.png', dpi=300)
```
圖6
咋看起來沒問題,但是如果你仔細觀察左下角的圖例會發現前兩行範圍顏色是重複的,且數值範圍是錯亂的,這是`geopandas.GeoDataFrame.plot()`中涉及箱線圖法的一個小`bug`,遇到這種問題不用慌,如果你在上一篇文章中去我的`Github`倉庫檢視過創作圖29對應的程式碼,一定會想到既然`geopandas`自身有**bug**,那我們用`matplotlib`中的`mpatches`和`legend`自定義圖例就可以啦,而為了自定義的圖例色彩與`geopandas`映射出的保持一致,我們需要額外使用到`matplotlib`中的`get_cmap(cmap)`來製作可獨立匯出顏色的**cmap**方案例項,譬如我們這裡是`Reds`,就需要按照前面`bp`的有記錄數量的分層結果,從`Reds`中產生同樣5個檔次的顏色,具體操作過程如下:
```Python
import matplotlib.patches as mpatches
fig, ax = plt.subplots(figsize=(10, 10))
ax = data_with_geometry.to_crs(albers_proj).plot(ax=ax,
column='province_confirmedCount',
cmap='Reds',
missing_kwds={
"color": "lightgrey",
"edgecolor": "black",
"hatch": "////",
"label": "缺失值"
},
scheme='BoxPlot')
handles, labels = ax.get_legend_handles_labels() #get existing legend item handles and labels
ax = nine_lines.geometry.to_crs(albers_proj).plot(ax=ax,
edgecolor='grey',
linewidth=3,
alpha=0.4)
# 例項化cmap方案
cmap = plt.get_cmap('Reds')
# 得到mapclassify中BoxPlot的資料分層點
bp = mc.BoxPlot(temp['province_confirmedCount'])
bins = bp.bins
# 製作圖例對映物件列表
LegendElement = [mpatches.Patch(facecolor=cmap(_*0.25), label=f'{int(max(bins[_], 0))} - {int(bins[_+1])}')
for _ in range(5)] + \
[mpatches.Patch(facecolor='lightgrey', edgecolor='black', hatch='////', label='缺失值')]
# 將製作好的圖例對映物件列表匯入legend()中,並配置相關引數
ax.legend(handles = LegendElement, loc='lower left', fontsize=10, title='確診數量分級', shadow=True, borderpad=0.6)
ax.axis('off')
plt.suptitle('新型冠狀肺炎累計確診數量地區分佈(截至2020年03月04日)', fontsize=24) # 新增最高級別標題
plt.title('資料分層方法:BoxPlot', fontsize=18)
plt.tight_layout(pad=4.5) # 調整不同標題之間間距
ax.text(-2900000, 1250000, '* 原始資料來源:丁香園,\n其中臺灣及香港資料缺失') # 新增資料說明
fig.savefig('圖7.png', dpi=300)
```
圖7
可以看到,通過自定義圖例的方式,雖然麻煩了一點,但是我們不僅修復了圖例的**bug**,還為其添加了更加完善的細節,如圖形修改為矩形,範圍修改為整數。
### 2.1.2 EqualInterval
`EqualInterval`即等間距,是最簡單的一種分層方法,它在原資料最小值與最大值間以等間距的方式劃分出`k`個層次,`mapclassify`中對應等間距法的類為`EqualInterval()`:
```Python
bp = mc.EqualInterval(temp['province_confirmedCount'])
# 檢視資料分層結果
bp
```
圖8
可以看到對於分佈非常不均勻的新冠肺炎確診數量資料來說,這種方法表現得十分糟糕,中間三個類都沒有記錄落入,如果使用這種方法強行繪圖,效果就會類似上一篇文章中地區分佈圖部分,最開始那個糟糕的效果那樣只有湖北一個地方是最深的暗紅色,而其他地方皆為最淡的色階,這裡就不重複演示。
### 2.1.3 FisherJenks
在瞭解`mapclassify`中的`FisherJenks`之前,我們先來了解一下什麼是*Jenks Natural Breaks*:
- *Jenks Natural Breaks*
*Jenks Natural Breaks*旨在為1維資料計算合適的劃分點,使得不同組之間的差距儘可能大的同時組內差距儘可能小,其思路非常簡單,舉一個簡單的例子進行說明:
對於一組待分割的序列$X=[4, 5, 9, 10]$,現在需要為其找到將原始資料分為$k=2$部分的方法,那麼實際上就有$[4], [5, 9, 10]$、$[4, 5], [9, 10]$以及$[4, 5, 9],[10]$這三種切分方法,現定義*sum of squared deviations for array mean*(簡稱*SDAM*):
$$
SDAM=\sum_{i=1}^{n}(X_{i}-\bar{X})^{2}
$$
以及針對每一種資料分層方法,在其分出的每一組$G_{i}$上計算組內離差平方和並累加所有組的結果,定義為*sum of squared deviations for class means*(簡稱*SDCM_ALL*):
$$
SDCM\_ALL=\sum_{i=1}^{k}\sum_{j=1}^{|G_{i}|}(G_{ij}-\bar{G_{i}})^2
$$
有了$SDAM$和$SDAM\_ALL$,現在對分組優劣定義一個評判指標*goodness of variance fit*(簡稱*GVF*),取值範圍為$[0,1]$,越高越好:
$$
GVF=(SDAM-SCDM)/SDAM
$$
這樣我們就可以對每一種分組方案進行評價,譬如對我們上面簡單的例子:
$$
SDAM=(4-7)^2+(5-7)^2+(9-7)^2+(10-7)^2=26 \\
SDCM_{1}=[(4-4)^2]+[(5-8)^2+(9-8)^2+(10-8)^2]=14 \\
SDCM_{2}=[(4-4.5)^2+(5-4.5)^2]+[(9-9.5)^2+(10-9.5)^2]=1 \\
SDCM_{3}=[(4-6)^2+(5-6)^2+(9-6)^2]+[(10-10)^2]=14
$$
則對應各種方案的*GVF*計算如下:
$$
GVF_{1}=GVF_{3}=(26-14)/26=0.46 \\
GVF_{2}=(26-1)/26=0.96
$$
可以看出第三種方案$[4, 5], [9, 10]$的分層方法效果最好,也與我們對資料的直觀感覺相貼合,這就是*Jenks Natural Breaks*的基本思路,但這種暴力遍歷所有分組方案的做法對資料數量及選擇分組的個數很敏感,尤其是對分組數量,一旦分組數量過於多,待篩選計算的方案數量就變成了天文數字,下面我來告訴大家為什麼:
定義長度為$n$的序列$X=[x_{1},x_{2},...,x_{n}]$。且滿足$i\leq{j}$時$x_{i}\leq{x_{j}}$,即整個序列從小到大單調遞增,那麼將其分成$k$組的過程,可以分解為先選擇第一組,且為了保證右邊剩餘$k-1$個組每組至少有1個數據分配,則第一組有$n-k+1$種分配方式,而第一組包含的數字數量$n_{1}$確定之後,剩餘$n-n_{1}$個數據的繼續分組又可以視為獨立的遞迴分組過程,因此最終需要考慮的方案個數用公式表達起來有些複雜,但是換成計算機中的*遞迴*過程就變得一目瞭然,我經過思考和紙上的推演,寫出了下面所示的遞迴函式`f(n, k)`來實現方案總數的計算:
```Python
def f(n, k):
# 若k退化為2,則顯然需要n - 1種方案,譬如4個數字分2組有3種方案
if k == 2:
return n - 1
else:
# 若k未退化為2,則繼續遞迴過程
return sum([f(n-_, k-1) for _ in range(1, n - k + 2)])
```
有了這個遞迴函式,我們就可以來直觀的看一看為什麼不能選擇太多分組,首先我們對長度為100的序列分為5組試試:
```Python
f(100, 3)
Out[11]: 4851
```
可以看到待選擇的方案才4851個,還是很少的,那麼我們接下來將組數提高到5:
```Python
f(100, 5)
Out[12]: 3764376
```
發生了什麼?隨著遞迴深度的增大,待選擇方案數量一下子就提高到三百多萬個!再切換成7試一下:
```Python
f(100, 7)
Out[13]: 1120529256
```
在跑上述程式碼時,明顯能感受到計算花費時間的激增,最終結果也達到驚人的11億多!看到這,我們就明白了,原始的*Jenks Natural Breaks*演算法雖然很有效,但如果以暴力遍歷的方式計算,其複雜度是難以應付日常需求的,為了對其進行優化,以在少量的計算時間內計算出儘可能靠譜的分組結果,一系列改良加速方法被提出,而`mapclassify`中的`FisherJenks`,即為*jenks*教授在論文*Fisher, W. D., 1958, On grouping for maximum homogeneity.*的基礎上提出的改良演算法,但這是一個很神祕的演算法,根據https://macwright.org/2013/02/18/literate-jenks.html 中的介紹,*jenks*教授的原始論文沒有留下數字化資料,一直為堪薩斯大學地理學系所私有,而隨著1996年*jenks*教授的離世,原論文需要到2072年版權才能到期公開,所以我們現在在各種`GIS`類軟體以及各種開源軟體包中使用到的`fisher jenks`演算法,均是對最初的一段`Fortran`程式碼的移植和改造,這也成了一段未解之謎,感興趣的讀者可以去https://stat.ethz.ch/pipermail/r-sig-geo/2006-March/000811.html 瞭解更多。
回到我們的主題,搞清楚了`FisherJenks`的計算目標之後,我們同樣利用`mapclassify`計算分層結果,其預設分層為5:
圖9
可以看到,在這種方式下,資料的分組較為合理,同樣將`geopandas.GeoDataFrame.plot()`中的引數設定為`FisherJenks`繪製出圖10:
圖10
與`BoxPlot`相比差距還是比較明顯,處於第二級嚴重程度的省份只有河南、廣東及浙江,更貼近資料的自然層次結構。
### 2.1.4 NaturalBreaks
等下!上一小結中的`FisherJenks`不就是我們俗稱的自然斷點法嗎,怎麼又來了個`NaturalBreaks`?其實我在翻看`mapclassify`的官方文件看到這裡時,也很疑惑,於是我仔細研究了`NaturalBreaks`對應的原始碼,追根溯源,WHAT?,竟然是`k-mean`演算法,而且直接呼叫的`scikit-learn`的`KMEANS`。。。
圖11
不過也可以理解,畢竟`k-means`就是在找資料中組內相似度儘可能高且組間差異儘量大的簇,關於`k-means`我想我就不需要贅述了,畢竟是最基礎的資料探勘演算法之一,而`scikit-learn`裡預設的`KMEANS`使用的`k-means++`初始方式,只是在原始`k-means`基礎上,修改了後續初始點的概率密度,使得`k-means`演算法更加魯棒穩定,下面直接來看`NaturalBreaks`的資料分層結果:
圖12
和`FisherJenks`的結果竟然一樣,但如果你多執行幾次會發現這個結果不是完全固定的,由於`k-means`隨機初始迭代起點,因此不同次執行的結果可能會有輕微差別(圖13),在資料量很大時,基於快速聚類法的`NaturalBreaks`是較為理想的資料分層選擇:
圖13
配合`geopandas`繪圖只需要把`scheme`引數修改為`NaturalBreaks`即可,因為跟`FisherJenks`類似,這裡就不再贅述。
### 2.1.5 JenksCaspall
`mapclassify`中的`JenksCaspall`本質上為`k-medians`聚類,其首先根據分層層數$k$在資料中找到$k-1$個分位數點,將原始資料等分為數量儘可能相同的$k$份並以這$k$份資料的中位數作為各自的初始點,接著基於`k-medians`的思想,迭代計算為每個樣本點找到與其距離更近的中位數點,並以此重新劃分分層以及重新計算各分層中位數點,直至每個資料對應的分層標籤不再變化,再將每個分層中資料的最大值作為間斷點,下面我們從`mapclassify`原始碼中抽出該部分程式碼,對其迭代過程視覺化,具體的程式碼較多,請在文章開頭的`Github`倉庫中對應本文路徑下檢視:
圖14
其中顏色區分對應迭代輪次的資料分層歸屬,虛線代表對應迭代輪次的間斷點,仔細可以看出在迭代過程中資料分層的變化情況。
用`JenksCaspall`資料分層出來的結果,無論資料分佈如何,每個分層內部的資料個數都較為均勻,下面我們用`JenksCaspall`來劃分省份疫情嚴重情況:
圖15
可以看到被分到最嚴重級別的不再只有湖北省,當你希望資料分層個數較為均勻時,`JenksCaspall`是個不錯的選擇。
### 2.1.6 HeadTailBreaks
`HeadTailBreaks`是一種較為嶄新的資料分層方法,出自*Head/Tail Breaks: A New Classification Scheme for Data with a Heavy-Tailed Distribution*(https://www.tandfonline.com/doi/abs/10.1080/00330124.2012.700499),專門用於對具有重尾特點的資料進行分層,所謂重尾即在整個資料中,較小的值數量往往較多,而最大的位於頭部的值數量很少,其資料分佈呈現出“尾重頭輕”的特點:
圖16
這種典型如人口密度分佈資料,數值較低的點往往數量眾多,聚集在尾部,形成重尾,`HeadTailBreaks`的優點是可以儘量在地區分佈圖中真實反映原始資料的分佈特點,如圖17(https://sites.google.com/site/thepowerofcartography/head-tail-breaks),左邊是`FisherJenks`,右邊是`HeadTailBreaks`,可以看出,右圖相對於左圖更好地體現了原始資料的重尾特點,最淺色的圖斑數量明顯多於次淺色的圖斑:
圖17
在`geopandas`中使用時傳入`scheme='HeadTailBreaks'`即可(由於新冠肺炎各省份確診數量資料尾部和頭部最大值之間沒有較為連續的中間值過渡,不太適合用此方法故不作演示)。
### 2.1.7 Quantiles
`Quantiles`即分位數,原理很簡單,根據分位數點對原資料進行等分:
圖18
利用`Quantiles`對確診數量分組視覺化:
圖19
### 2.1.8 Percentiles
同樣是使用分位數對資料進行分層,`Percentiles`提供了引數`pct`以允許使用者以百分位數的形式傳入自定義分隔點,譬如我們將`[1, 50, 99, 100]`作為`pct`的傳入值,則分組結果如下:
圖20
每個傳入的百分位點其左邊到上一個分隔點為止,包括其本身,將被分到同一組,對應的影象如圖21,在`geopandas`中使用時除了設定`scheme='Percentiles'`之外,還要在另一個字典型引數`classification_kwds`中傳入`{'pct': 百分位數列表}`:
圖21
### 2.1.9 StdMean
`StdMean`的思想類似前面的箱線圖,不同的是箱線圖屬於非引數方法,而`StdMean`建立在正態分佈為基礎的經驗法則之上,即對於正態分佈而言,**68%的資料將分佈在距離均值1個標準差之內,95%的資料在2個標準差之內,99.7%的資料在3個標準差之內**,即對原始資料標準化之後,根據距離樣本均值的不同標準差範圍來劃分資料,`mapclassify`中的`StdMean`預設按照`[-2, -1, 1, 2]`來劃分:
圖22
### 2.1.10 UserDefined
關於資料分層最後要介紹的是自定義分層,即按照使用者輸入的分隔點來自由劃分資料集,譬如我們按照新浪新聞疫情地圖的劃分方式:
圖23
結合`geopandas`使用時除了設定`scheme='UserDefined'`以外,還要設定`classification_kwds`中的`bins=分隔點列表`:
```Python
fig, ax = plt.subplots(figsize=(10, 10))
ax = data_with_geometry.to_crs(albers_proj).plot(ax=ax,
column='province_confirmedCount',
cmap='Reds',
missing_kwds={
"color": "lightgrey",
"edgecolor": "black",
"hatch": "////",
"label": "缺失值"
},
legend=True,
scheme='UserDefined',
classification_kwds={
'bins': [9, 99, 499, 999, 9999]
},
legend_kwds={
'loc': 'lower left',
'title': '確診數量分級',
'shadow': True
})
ax = nine_lines.geometry.to_crs(albers_proj).plot(ax=ax,
edgecolor='grey',
linewidth=3,
alpha=0.4)
ax.axis('off')
plt.suptitle('新型冠狀肺炎累計確診數量地區分佈', fontsize=24) # 新增最高級別標題
plt.tight_layout(pad=4.5) # 調整不同標題之間間距
ax.text(-2800000, 1300000, '* 原始資料來源:丁香園,\n其中臺灣及香港資料缺失') # 新增資料說明
fig.savefig('圖24.png', dpi=300)
```
圖24
## 2.2 色彩方案的選擇
前面已經詳細介紹了資料分層常用的各種方法及使用場景,“分層”的部分做完之後,就到了設色的部分,其實色彩搭配是比較主觀的事情,但想要自己創造出美觀合理的配色方案並不是容易的事情,下面我們來介紹兩種選擇配色方案的方法。
### 2.2.1 基於palettable的配色
下面我要給大家介紹的`Python`第三方庫`palettable`在我之前關於詞雲圖的一篇文章中介紹`stylecloud`時介紹過,是專門幫助我們為視覺化作品配色的。
`palettable`不依賴其他三方庫,純`Python`實現,其強大之處在於內建了數量驚人的經典配色方案,囊括了*CartoColors*、*cmocean*、*Colorbrewer2*、*Cubehelix*、*Light & Bartlein*、*matplotlib*、*MyCarta*、*Scientific*、*Tableau*以及*The Wes Anderson Palettes blog*中的大量經典配色方案:
- [`palettable.cartocolors.diverging`](https://jiffyclub.github.io/palettable/cartocolors/diverging/)
- [`palettable.cartocolors.qualitative`](https://jiffyclub.github.io/palettable/cartocolors/qualitative/)
- [`palettable.cartocolors.sequential`](https://jiffyclub.github.io/palettable/cartocolors/sequential/)
- [`palettable.cmocean.diverging`](https://jiffyclub.github.io/palettable/cmocean/diverging/)
- [`palettable.cmocean.sequential`](https://jiffyclub.github.io/palettable/cmocean/sequential/)
- [`palettable.colorbrewer.diverging`](https://jiffyclub.github.io/palettable/colorbrewer/diverging/)
- [`palettable.colorbrewer.qualitative`](https://jiffyclub.github.io/palettable/colorbrewer/qualitative/)
- [`palettable.colorbrewer.sequential`](https://jiffyclub.github.io/palettable/colorbrewer/sequential/)
- [`palettable.lightbartlein.diverging`](https://jiffyclub.github.io/palettable/lightbartlein/diverging/)
- [`palettable.lightbartlein.sequential`](https://jiffyclub.github.io/palettable/lightbartlein/sequential/)
- [`palettable.matplotlib`](https://jiffyclub.github.io/palettable/matplotlib/)
- [`palettable.mycarta`](https://jiffyclub.github.io/palettable/mycarta/)
- [`palettable.scientific.diverging`](https://jiffyclub.github.io/palettable/scientific/diverging/)
- [`palettable.scientific.sequential`](https://jiffyclub.github.io/palettable/scientific/sequential/)
- [`palettable.tableau`](https://jiffyclub.github.io/palettable/tableau/)
- [`palettable.wesanderson`](https://jiffyclub.github.io/palettable/wesanderson/)
使用起來非常簡單,譬如如果我們想要使用`palettable.cmocean.sequential`中的色彩,其中`cmocean`表示色彩來源,`sequential`表示連續型色彩,就可以先在對應的[示例網頁](https://jiffyclub.github.io/palettable/cmocean/sequential/)下檢視所有方案:
圖25
比如我對其中的`Dense`方案很中意:
圖26
就可以按照如下方式,先從`palettable`中匯入對應顏色,譬如我們匯入`Dense_20`,20表示其自帶的離散色彩數量,並檢視其自帶的離散色彩RGB值、離散色盤以及連續色盤示例:
```Python
from palettable.cmocean.sequential import Dense_20
from pprint import pprint
print('對應離散顏色:')
pprint(Dense_20.colors)
print('離散:')
Dense_20.show_discrete_image()
print('連續:')
Dense_20.show_continuous_image()
```
圖27
使用`.mpl_colormap`將其轉換為`matplotlib`可接受的cmap資料結構,作為`cmap`引數值傳入繪圖部分即可:
圖28
如果想要翻轉對映方向,換成`Dense_20_r`再重複上述操作即可:
圖29
更多`palettable`自帶色彩方案,可以在https://jiffyclub.github.io/palettable/ 下檢視探索。
### 2.2.2 基於圖片主色的配色
我們在生活中偶然會看到配色方案讓人眼前一亮的海報或畫作,這時如果你想將這些作品中的主要顏色也應用到自己的視覺化作品上,可以參考我下面的做法,這裡以我很喜歡的賈樟柯導演的《一直游到海水變藍》中文版海報為例:
圖30
思路是抽取所有畫素點的RGB三通道值,分別作為三個特徵,輸入`k-means`中進行聚類,將聚類數量設定為你想要提取出的主色數量:
```Python
from sklearn.cluster import KMeans
# 構建特徵
rgb = pd.DataFrame([sea[x][y] for x in range(sea.shape[0]) for y in range(sea.shape[1])],
columns=['r', 'g', 'b'])
# k-means聚類,其中n_clusters表示聚類數量,n_jobs=-1表示開啟所有核心並行運算
model = KMeans(n_clusters=5, n_jobs=-1)
model.fit(rgb) # 訓練模型
# 提取聚類簇重心,即我們需要的主色,繪製調色盤
plt.bar([i for i in range(model.cluster_centers_.__len__())],
height=[1 for i in range(model.cluster_centers_.__len__())],
color=[tuple(c) for c in (model.cluster_centers_ / 255.)],
width=1)
plt.axis('off')
```
圖31
再來個例子,提取《一直游到海水變藍》海外版海報主色:
圖32
對應提取到的5種主色如圖33:
圖33
類似的,你可以試著提取你喜愛的平面作品的主色。
以上就是本文的全部內容,如有筆誤望