皮一下,就很開心😳
「那麼到底如何在 Python 中繪圖?」
曾經這個問題有一個簡單的答案:Matplotlib 是唯一的辦法。
如今,Python 作為數據科學的語言,有著更多的選擇。你應該用什麼呢?
今天向你展示四個最流行的 Python 繪圖庫:Matplotlib、Seaborn、Plotly 和 Bokeh。
再加上兩個值得考慮的優秀的後起之秀:Altair,擁有豐富的 API;Pygal,擁有漂亮的 SVG 輸出。
我還會看看 Pandas 提供的非常方便的繪圖 API。
示例繪圖
每個庫都採取了稍微不同的方法來繪製數據。
為了比較它們,我將用每個庫繪製同樣的圖,並給你展示原始碼。對於示例數據,我選擇了這張 1966 年以來英國大選結果的分組柱狀圖。
Bar chart of British election data
Matplotlib
Matplotlib 是最古老的 Python 繪圖庫,現在仍然是最流行的。
它創建於 2003 年,是 SciPy Stack 的一部分,SciPy Stack 是一個類似於 Matlab 的開源科學計算庫。
Matplotlib 為你提供了對繪製的精確控制。例如,你可以在你的條形圖中定義每個條形圖的單獨的 X 位置。下面是繪製這個圖表的代碼(你可以在這裡運行):
import matplotlib.pyplot as pltfrom votes import wide asdf#Initialise a figure. subplots() withno args gives one plot.# A little data preparation x = np.arange(len(years))#Plot each bar plot. Note: manually calculating the 'dodges' of the bars ax.bar(x - 3*width/2, df['conservative'], width, label='Conservative', color='#0343df') ax.bar(x - width/2, df['labour'], width, label='Labour', color='#e50000') ax.bar(x + width/2, df['liberal'], width, label='Liberal', color='#ffff14') ax.bar(x + 3*width/2, df['others'], width, label='Others', color='#929591')#Customise some display properties ax.set_title('UK election results') ax.set_xticks(x) #This ensures we have one tick per year, otherwise we get fewer ax.set_xticklabels(years.astype(str).values, rotation='vertical')#AskMatplotlib to show the plot
這是用 Matplotlib 繪製的選舉結果:
Matplotlib plot of British election data
Seaborn
Seaborn 是 Matplotlib 之上的一個抽象層;它提供了一個非常整潔的界面,讓你可以非常容易地製作出各種類型的有用繪圖。
不過,它並沒有在能力上有所妥協!Seaborn 提供了訪問底層 Matplotlib 對象的逃生艙口,所以你仍然可以進行完全控制。
Seaborn 的代碼比原始的 Matplotlib 更簡單(可在此處運行):
from votes importlongasdf#Some boilerplate to initialise things#Thisiswhere the actual plot gets made ax = sns.barplot(data=df, x="year", y="seats", hue="party", palette=['blue', 'red', 'yellow', 'grey'], saturation=0.6)#Customise some display properties ax.set_title('UK election results') ax.set_xticklabels(df["year"].unique().astype(str), rotation='vertical')#AskMatplotlib to show it
並生成這樣的圖表:
Seaborn plot of British election data
Plotly
Plotly 是一個繪圖生態系統,它包括一個 Python 繪圖庫。它有三個不同的接口:
2. 一個命令式接口,允許你使用類似 JSON 的數據結構來指定你的繪圖。3. 類似於 Seaborn 的高級接口,稱為 Plotly Express。Plotly 繪圖被設計成嵌入到 Web 應用程式中。Plotly 的核心其實是一個 JavaScript 庫!它使用 D3 和 stack.gl 來繪製圖表。
你可以通過向該 JavaScript 庫傳遞 JSON 來構建其他語言的 Plotly 庫。官方的 Python 和 R 庫就是這樣做的。在 Anvil,我們將 Python Plotly API 移植到了 Web 瀏覽器中運行。
這是使用 Plotly 的原始碼(你可以在這裡運行):
import plotly.graph_objects as gofrom votes import wide asdf#Get a convenient list of x-values x = list(range(len(years))) go.Bar(x=x, y=df['conservative'], name='Conservative', marker=go.bar.Marker(color='#0343df')), go.Bar(x=x, y=df['labour'], name='Labour', marker=go.bar.Marker(color='#e50000')), go.Bar(x=x, y=df['liberal'], name='Liberal', marker=go.bar.Marker(color='#ffff14')), go.Bar(x=x, y=df['others'], name='Others', marker=go.bar.Marker(color='#929591')),#Customise some display properties title=go.layout.Title(text="Election results", x=0.5), xaxis_tickvals=list(range(27)), xaxis_ticktext=tuple(df['year'].values), fig = go.Figure(data=bar_plots, layout=layout)
選舉結果圖表:
Plotly plot of British election data
Bokeh
Bokeh(發音為 「BOE-kay」)擅長構建交互式繪圖,所以這個標準的例子並沒有將其展現其最好的一面。和 Plotly 一樣,Bokeh 的繪圖也是為了嵌入到 Web 應用中,它以 HTML 文件的形式輸出繪圖。
下面是使用 Bokeh 的代碼(你可以在這裡運行):
from bokeh.io import show, output_filefrom bokeh.models importColumnDataSource, FactorRange, HoverToolfrom bokeh.plotting import figurefrom bokeh.transform import factor_cmapfrom votes importlongasdf#Specify a file to write the plot to output_file("elections.html")#Tuples of groups(year, party) x = [(str(r[1]['year']), r[1]['party']) for r indf.iterrows()]#Bokeh wraps your data in its own objects to support interactivity source = ColumnDataSource(data=dict(x=x, y=y))'Conservative': '#0343df', fill_color = factor_cmap('x', palette=list(cmap.values()), factors=list(cmap.keys()), start=1, end=2) p = figure(x_range=FactorRange(*x), width=1200, title="Election results") p.vbar(x='x', top='y', width=0.9, source=source, fill_color=fill_color, line_color=fill_color)#Customise some display properties p.x_range.range_padding = 0.1 p.yaxis.axis_label = 'Seats' p.xaxis.major_label_orientation = 1 p.xgrid.grid_line_color = None
圖表如下:
Bokeh plot of British election data
Altair
Altair 是基於一種名為 Vega 的聲明式繪圖語言(或「可視化語法」)。這意味著它具有經過深思熟慮的 API,可以很好地擴展複雜的繪圖,使你不至於在嵌套循環的地獄中迷失方向。
與 Bokeh 一樣,Altair 將其圖形輸出為 HTML 文件。這是代碼(你可以在這裡運行):
from votes importlongasdf'Conservative': '#0343df',df['year'] = df['year'].astype(str)#Here's where we make the plot chart = alt.Chart(df).mark_bar().encode( x=alt.X('party', title=None), column=alt.Column('year', sort=list(df['year']), title=None), color=alt.Color('party', scale=alt.Scale(domain=list(cmap.keys()), range=list(cmap.values()))) # Save it as an HTML file. chart.save('altair-elections.html')
結果圖表:
Altair plot of British election data
Pygal
Pygal 專注於視覺外觀。它默認生成 SVG 圖,所以你可以無限放大它們或列印出來,而不會被像素化。Pygal 繪圖還內置了一些很好的交互性功能,如果你想在 Web 應用中嵌入繪圖,Pygal 是另一個被低估了的候選者。
代碼是這樣的(你可以在這裡運行它):
from pygal.style importStylefrom votes import wide asdf colors=('#0343df', '#e50000', '#ffff14', '#929591') font_family='Roboto,Helvetica,Arial,sans-serif', background='transparent',#Set up the bar plot, ready for data title="UK Election Results",#Add four data sets to the bar plot c.add('Conservative', df['conservative']) c.add('Labour', df['labour']) c.add('Liberal', df['liberal']) c.add('Others', df['others'])#Writethis to an SVG file c.render_to_file('pygal.svg')
繪製結果:
Pygal plot of British election data
Pandas
Pandas 是 Python 的一個極其流行的數據科學庫。它允許你做各種可擴展的數據處理,但它也有一個方便的繪圖 API。因為它直接在數據幀上操作,所以 Pandas 的例子是本文中最簡潔的代碼片段,甚至比 Seaborn 的代碼還要短!
Pandas API 是 Matplotlib 的一個封裝器,所以你也可以使用底層的 Matplotlib API 來對你的繪圖進行精細的控制。
這是 Pandas 中的選舉結果圖表。代碼精美簡潔!
from matplotlib.colors importListedColormapfrom votes import wide asdf cmap = ListedColormap(['#0343df', '#e50000', '#ffff14', '#929591']) ax = df.plot.bar(x='year', colormap=cmap) ax.set_title('UK election results')
繪圖結果:
Python 提供了許多繪製數據的方法,無需太多的代碼。
雖然你可以通過這些方法快速開始創建你的繪圖,但它們確實需要一些本地配置。
下面分享幾個實例操作:
1. 散點圖
Scatteplot是用於研究兩個變量之間關係的經典和基本圖。如果數據中有多個組,則可能需要以不同顏色可視化每個組。在Matplotlib,你可以方便地使用。
# Import dataset
midwest = pd.read_csv("https://raw.githubusercontent.com/selva86/datasets/master/midwest_filter.csv")
# Prepare Data
# Create as many colors as there are unique midwest['category']
categories = np.unique(midwest['category'])
colors = [plt.cm.tab10(i/float(len(categories)-1)) for i in range(len(categories))]
# Draw Plot for Each Category
plt.figure(figsize=(16, 10), dpi= 80, facecolor='w', edgecolor='k')
for i, category in enumerate(categories):
plt.scatter('area', 'poptotal',
data=midwest.loc[midwest.category==category, :],
s=20, c=colors[i], label=str(category))
# Decorations
plt.gca().set(xlim=(0.0, 0.1), ylim=(0, 90000),
xlabel='Area', ylabel='Population')
plt.xticks(fontsize=12); plt.yticks(fontsize=12)
plt.title("Scatterplot of Midwest Area vs Population", fontsize=22)
plt.legend(fontsize=12)
plt.show()
2. 帶邊界的氣泡圖
有時,您希望在邊界內顯示一組點以強調其重要性。在此示例中,您將從應該被環繞的數據幀中獲取記錄,並將其傳遞給下面的代碼中描述的記錄。encircle()
from matplotlib import patches
from scipy.spatial import ConvexHull
import warnings; warnings.simplefilter('ignore')
sns.set_style("white")
# Step 1: Prepare Data
midwest = pd.read_csv("https://raw.githubusercontent.com/selva86/datasets/master/midwest_filter.csv")
# As many colors as there are unique midwest['category']
categories = np.unique(midwest['category'])
colors = [plt.cm.tab10(i/float(len(categories)-1)) for i in range(len(categories))]
# Step 2: Draw Scatterplot with unique color for each category
fig = plt.figure(figsize=(16, 10), dpi= 80, facecolor='w', edgecolor='k')
for i, category in enumerate(categories):
plt.scatter('area', 'poptotal', data=midwest.loc[midwest.category==category, :], s='dot_size', c=colors[i], label=str(category), edgecolors='black', linewidths=.5)
# Step 3: Encircling
# https://stackoverflow.com/questions/44575681/how-do-i-encircle-different-data-sets-in-scatter-plot
def encircle(x,y, ax=None, **kw):
if not ax: ax=plt.gca()
p = np.c_[x,y]
hull = ConvexHull(p)
poly = plt.Polygon(p[hull.vertices,:], **kw)
ax.add_patch(poly)
# Select data to be encircled
midwest_encircle_data = midwest.loc[midwest.state=='IN', :]
# Draw polygon surrounding vertices
encircle(midwest_encircle_data.area, midwest_encircle_data.poptotal, ec="k", fc="gold", alpha=0.1)
encircle(midwest_encircle_data.area, midwest_encircle_data.poptotal, ec="firebrick", fc="none", linewidth=1.5)
# Step 4: Decorations
plt.gca().set(xlim=(0.0, 0.1), ylim=(0, 90000),
xlabel='Area', ylabel='Population')
plt.xticks(fontsize=12); plt.yticks(fontsize=12)
plt.title("Bubble Plot with Encircling", fontsize=22)
plt.legend(fontsize=12)
plt.show()
3. 帶線性回歸最佳擬合線的散點圖
如果你想了解兩個變量如何相互改變,那麼最合適的線就是要走的路。下圖顯示了數據中各組之間最佳擬合線的差異。要禁用分組並僅為整個數據集繪製一條最佳擬合線,請從下面的調用中刪除該參數。
# Import Data
df = pd.read_csv("https://raw.githubusercontent.com/selva86/datasets/master/mpg_ggplot2.csv")
df_select = df.loc[df.cyl.isin([4,8]), :]
# Plot
sns.set_style("white")
gridobj = sns.lmplot(x="displ", y="hwy", hue="cyl", data=df_select,
height=7, aspect=1.6, robust=True, palette='tab10',
scatter_kws=dict(s=60, linewidths=.7, edgecolors='black'))
# Decorations
gridobj.set(xlim=(0.5, 7.5), ylim=(0, 50))
plt.title("Scatterplot with line of best fit grouped by number of cylinders", fontsize=20)
每個回歸線都在自己的列中。
或者,您可以在其自己的列中顯示每個組的最佳擬合線。你可以通過在裡面設置參數來實現這一點。
# Import Data
df = pd.read_csv("https://raw.githubusercontent.com/selva86/datasets/master/mpg_ggplot2.csv")
df_select = df.loc[df.cyl.isin([4,8]), :]
# Each line in its own column
sns.set_style("white")
gridobj = sns.lmplot(x="displ", y="hwy",
data=df_select,
height=7,
robust=True,
palette='Set1',
col="cyl",
scatter_kws=dict(s=60, linewidths=.7, edgecolors='black'))
# Decorations
gridobj.set(xlim=(0.5, 7.5), ylim=(0, 50))
plt.show()
4. 抖動圖
通常,多個數據點具有完全相同的X和Y值。結果,多個點相互繪製並隱藏。為避免這種情況,請稍微抖動點,以便您可以直觀地看到它們。這很方便使用
# Import Data
df = pd.read_csv("https://raw.githubusercontent.com/selva86/datasets/master/mpg_ggplot2.csv")
# Draw Stripplot
fig, ax = plt.subplots(figsize=(16,10), dpi= 80)
sns.stripplot(df.cty, df.hwy, jitter=0.25, size=8, ax=ax, linewidth=.5)
# Decorations
plt.title('Use jittered plots to avoid overlapping of points', fontsize=22)
plt.show()
5. 計數圖
避免點重疊問題的另一個選擇是增加點的大小,這取決於該點中有多少點。因此,點的大小越大,周圍的點的集中度就越大。
# Import Data
df = pd.read_csv("https://raw.githubusercontent.com/selva86/datasets/master/mpg_ggplot2.csv")
df_counts = df.groupby(['hwy', 'cty']).size().reset_index(name='counts')
# Draw Stripplot
fig, ax = plt.subplots(figsize=(16,10), dpi= 80)
sns.stripplot(df_counts.cty, df_counts.hwy, size=df_counts.counts*2, ax=ax)
# Decorations
plt.title('Counts Plot - Size of circle is bigger as more points overlap', fontsize=22)
plt.show()
6. 邊緣直方圖
邊緣直方圖具有沿X和Y軸變量的直方圖。這用於可視化X和Y之間的關係以及單獨的X和Y的單變量分布。該圖如果經常用於探索性數據分析(EDA)。
# Import Data
df = pd.read_csv("https://raw.githubusercontent.com/selva86/datasets/master/mpg_ggplot2.csv")
# Create Fig and gridspec
fig = plt.figure(figsize=(16, 10), dpi= 80)
grid = plt.GridSpec(4, 4, hspace=0.5, wspace=0.2)
# Define the axes
ax_main = fig.add_subplot(grid[:-1, :-1])
ax_right = fig.add_subplot(grid[:-1, -1], xticklabels=[], yticklabels=[])
ax_bottom = fig.add_subplot(grid[-1, 0:-1], xticklabels=[], yticklabels=[])
# Scatterplot on main ax
ax_main.scatter('displ', 'hwy', s=df.cty*4, c=df.manufacturer.astype('category').cat.codes, alpha=.9, data=df, cmap="tab10", edgecolors='gray', linewidths=.5)
# histogram on the right
ax_bottom.hist(df.displ, 40, histtype='stepfilled', orientation='vertical', color='deeppink')
ax_bottom.invert_yaxis()
# histogram in the bottom
ax_right.hist(df.hwy, 40, histtype='stepfilled', orientation='horizontal', color='deeppink')
# Decorations
ax_main.set(title='Scatterplot with Histograms displ vs hwy', xlabel='displ', ylabel='hwy')
ax_main.title.set_fontsize(20)
for item in ([ax_main.xaxis.label, ax_main.yaxis.label] + ax_main.get_xticklabels() + ax_main.get_yticklabels()):
item.set_fontsize(14)
xlabels = ax_main.get_xticks().tolist()
ax_main.set_xticklabels(xlabels)
plt.show()