資料科學 教學 視覺化 PYTHON工具

Python 資料視覺化入門:Matplotlib 與 Plotly 實戰教學

Python 資料視覺化入門 - Matplotlib & Plotly

目錄

  1. 資料視覺化的重要性
  2. 簡介 Matplotlib 和 Plotly
  3. Matplotlib 基礎
  4. Plotly 基礎

1. 資料視覺化的重要性

為什麼我們需要資料視覺化?

  • 發現隱藏的模式和趨勢:人類大腦天生擅長處理視覺資訊,透過圖表可以快速識別出數據中的模式和趨勢
  • 簡化複雜數據:將龐大的數據集轉換成視覺化圖表,能夠幫助我們理解其中的複雜關係
  • 有效溝通:圖表是溝通分析結果的強大工具,能夠讓非專業人士也能理解複雜的數據故事
  • 輔助決策:好的視覺化能夠提供直觀的證據支持,協助做出明智的決策
  • 發現異常值:視覺化可以快速幫助我們找出數據中的異常點或離群值

案例:Anscombe’s Quartet

Anscombe’s Quartet

這四組數據集有幾乎相同的統計特性(平均值、標準差、相關係數等),但視覺化後呈現出完全不同的模式。

同學們,今天我們來認識一個經典的統計學與資料科學教材──Anscombe 四重奏,它清楚地提醒我們:光看數字摘要(平均值、標準差、相關係數等)是不夠的,資料視覺化是不可或缺的一步。


四重奏的共同統計特性

這四組 (x₁,y₁)、(x₂,y₂)、(x₃,y₃)、(x₄,y₄) 資料集雖然圖形看起來完全不同,卻在以下幾個統計量上幾乎相同:

  • x 與 y 的平均值
  • x 與 y 的標準差
  • x 與 y 之間的線性相關係數
  • 根據最小平方法所擬合的直線(y = a + b x)的截距 a 和斜率 b

若只看這些數字指標,你會認為它們的分布和關係都差不多;但實際上……


四張圖的不同特徵

圖片 特徵描述
圖 1 (x₁, y₁) 這是最「理想」的線性關係:點大致分布在一條直線附近,誤差(殘差)分布均勻。
圖 2 (x₂, y₂) 真正的關係其實是二次函數:散點呈現明顯的曲線趨勢,但線性回歸線仍有不錯的統計指標。
圖 3 (x₃, y₃) 幾乎所有點都在線性趨勢上,但有一個異常值 (outlier) 極度偏離,這個單點強烈影響了回歸線的位置。
圖 4 (x₄, y₄) x 幾乎都一樣(集中在同一個值),只有一個點的 x 值極大,這個單一極端值決定了整個相關係數與回歸斜率。

我們學到什麼?

  1. 統計摘要有其限制 平均、標準差、相關係數等數值能快速概述資料,但可能掩蓋極端值或非線性趨勢。

  2. 視覺化不可或缺 透過散佈圖、殘差圖等視覺化工具,可以立刻看出非線性、群聚、離群點等特徵,避免誤用錯誤模型。

  3. 小心異常值與極端值 單一觀測值就有可能左右整體分析結果,需進一步檢查資料品質,決定是否應排除或另行處理。


建議

  • 做線性回歸前,一定要先畫散布圖 (Scatter Plot)。
  • 若發現曲線趨勢,可考慮多項式回歸或非線性模型。
  • 遇到疑似離群點,先檢查資料來源,再決定如何處理(如剔除、修正或加權)。
  • 養成以視覺化檢視資料的習慣,避免「黑箱式」的盲目套用統計模型。
  • 使用視覺化工具(如 Matplotlib、Plotly)來輔助資料分析,讓數據故事更具說服力。

同學們,記住:「數字會說謊,眼睛不會」,讓我們下次見到資料時,都先動手畫圖,深刻理解它的特性!

2. 簡介 Matplotlib 和 Plotly

Matplotlib

  • 特點:靜態、經典、高度客製化
  • 適用場景:科學研究、學術論文、報告、需要精確控制的圖表
  • 優勢
    • Python 生態系中最受歡迎的繪圖庫之一
    • 與 NumPy 和 Pandas 完美整合
    • 高度客製化,幾乎每個元素都可以調整
    • 廣泛的社群支持和豐富的文檔
  • 缺點
    • 預設風格較為基礎,需要較多代碼進行美化
    • 互動性有限
    • 學習曲線較陡峭

Plotly

  • 特點:互動式、適用於 Web、美觀
  • 適用場景:Web 應用、儀表板、需要互動性的資料展示、商業報告
  • 優勢
    • 高度互動性(縮放、懸停資訊、選擇等)
    • 美觀的預設樣式
    • 可以輕鬆整合到 Web 應用中
    • Plotly Express 提供簡潔的 API
  • 缺點
    • 需要連接網路才能使用部分功能
    • 對於某些複雜的客製化可能較為困難
    • 在學術出版物中較少使用

3. Matplotlib 基礎

基本結構

Matplotlib 的核心是 Figure 和 Axes 對象:

  • Figure:整個圖表容器,可以包含多個 Axes
  • Axes:實際的繪圖區域,包含 x 軸、y 軸和繪圖數據
import matplotlib.pyplot as plt

# 創建 Figure 和 Axes 對象
fig, ax = plt.subplots()

# 或者使用 pyplot 接口(自動創建 Figure 和 Axes)
plt.plot([1, 2, 3, 4], [1, 4, 9, 16])

繪圖流程

最基本的 Matplotlib 繪圖流程如下:

import matplotlib.pyplot as plt
import numpy as np

# 準備數據
x = np.linspace(0, 10, 100)  # 0 到 10 之間的 100 個等距點
y = np.sin(x)                # 對應的 sine 值

# 創建圖表
plt.figure(figsize=(8, 4))   # 設置圖表大小(可選)
plt.plot(x, y)               # 繪製線圖

# 顯示圖表
plt.show()

添加圖表元素

import matplotlib.pyplot as plt
import numpy as np

# 準備數據
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

# 創建圖表
plt.figure(figsize=(10, 6))
plt.plot(x, y1, label='sin(x)')  # 添加圖例標籤
plt.plot(x, y2, label='cos(x)')

# 添加標題和標籤
plt.title('Sin and Cos Functions', fontsize=15)  # 標題
plt.xlabel('x', fontsize=12)                     # x 軸標籤
plt.ylabel('y', fontsize=12)                     # y 軸標籤

# 添加圖例
plt.legend(fontsize=12)

# 添加網格
plt.grid(True, linestyle='--', alpha=0.7)

# 自定義 x 和 y 軸範圍
plt.xlim(0, 10)
plt.ylim(-1.5, 1.5)

plt.show()

常見圖表類型

線圖 (Line Plot)

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 10, 100)
y = np.sin(x)

plt.figure(figsize=(8, 4))
plt.plot(x, y, linestyle='-', color='blue', linewidth=2, marker='o', markersize=5, markevery=10)
plt.title('Line Plot Example')
plt.show()

散點圖 (Scatter Plot)

import matplotlib.pyplot as plt
import numpy as np

# 隨機生成數據
np.random.seed(42)
x = np.random.rand(50)
y = np.random.rand(50)
colors = np.random.rand(50)
sizes = 1000 * np.random.rand(50)

plt.figure(figsize=(8, 6))
plt.scatter(x, y, c=colors, s=sizes, alpha=0.6, cmap='viridis')
plt.colorbar()  # 添加顏色條
plt.title('Scatter Plot Example')
plt.show()

長條圖 (Bar Plot)

import matplotlib.pyplot as plt

categories = ['A', 'B', 'C', 'D', 'E']
values = [3, 7, 2, 5, 8]

plt.figure(figsize=(8, 6))
plt.bar(categories, values, color='skyblue', edgecolor='black')
plt.title('Bar Plot Example')
plt.xlabel('Categories')
plt.ylabel('Values')
plt.show()

子圖與布局 (Subplots and Layouts)

import matplotlib.pyplot as plt
import numpy as np

# 創建 2x2 的子圖
fig, axs = plt.subplots(2, 2, figsize=(12, 8))

# 子圖 1:線圖
x = np.linspace(0, 5, 100)
axs[0, 0].plot(x, np.sin(x))
axs[0, 0].set_title('Sine Function')

# 子圖 2:散點圖
np.random.seed(42)
x = np.random.rand(50)
y = np.random.rand(50)
axs[0, 1].scatter(x, y, c='red', alpha=0.6)
axs[0, 1].set_title('Scatter Plot')

# 子圖 3:長條圖
categories = ['A', 'B', 'C', 'D', 'E']
values = [3, 7, 2, 5, 8]
axs[1, 0].bar(categories, values)
axs[1, 0].set_title('Bar Plot')

# 子圖 4:直方圖
data = np.random.randn(1000)
axs[1, 1].hist(data, bins=30, alpha=0.7, color='green')
axs[1, 1].set_title('Histogram')

# 調整子圖間距
plt.tight_layout()
plt.show()

Matplotlib 參考資源

Matplotlib 動手練習範例

練習 1:基本線圖

繪製台灣近年平均氣溫趨勢圖。

import matplotlib.pyplot as plt
import numpy as np

# 假設數據:台灣 2015-2024 年的年平均氣溫 (攝氏度)
years = np.arange(2015, 2025)
temperatures = [24.2, 24.8, 24.5, 25.1, 25.3, 25.6, 24.9, 25.7, 25.4, 26.1]

plt.figure(figsize=(10, 6))
plt.plot(years, temperatures, marker='o', linestyle='-', color='red', linewidth=2)

plt.title('台灣年平均氣溫 (2015-2024)', fontsize=15)
plt.xlabel('年份', fontsize=12)
plt.ylabel('溫度 (°C)', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)

# 標註最高點和最低點
max_temp_idx = np.argmax(temperatures)
min_temp_idx = np.argmin(temperatures)

plt.annotate(f'最高: {temperatures[max_temp_idx]}°C', 
             xy=(years[max_temp_idx], temperatures[max_temp_idx]),
             xytext=(years[max_temp_idx]+0.5, temperatures[max_temp_idx]+0.3),
             arrowprops=dict(facecolor='black', shrink=0.05, width=1.5))

plt.annotate(f'最低: {temperatures[min_temp_idx]}°C', 
             xy=(years[min_temp_idx], temperatures[min_temp_idx]),
             xytext=(years[min_temp_idx]-1, temperatures[min_temp_idx]-0.5),
             arrowprops=dict(facecolor='black', shrink=0.05, width=1.5))

plt.xticks(years)
plt.ylim(23.5, 26.5)

plt.savefig('taiwan_temperature_trend.png', dpi=300, bbox_inches='tight')
plt.show()

練習 2:比較長條圖

比較不同程式語言的受歡迎程度。

import matplotlib.pyplot as plt
import numpy as np

# 數據:2024 年不同程式語言的受歡迎程度評分
languages = ['Python', 'JavaScript', 'Java', 'C++', 'Go', 'Rust', 'TypeScript']
ratings_2023 = [9.8, 8.5, 7.2, 6.8, 6.5, 5.8, 7.9]
ratings_2024 = [9.9, 8.3, 6.9, 7.0, 7.2, 6.9, 8.5]

# 設置長條的寬度和位置
bar_width = 0.35
x = np.arange(len(languages))

fig, ax = plt.subplots(figsize=(12, 7))

# 繪製長條圖
bar1 = ax.bar(x - bar_width/2, ratings_2023, bar_width, label='2023', color='skyblue', edgecolor='black')
bar2 = ax.bar(x + bar_width/2, ratings_2024, bar_width, label='2024', color='lightcoral', edgecolor='black')

# 添加圖表元素
ax.set_title('程式語言受歡迎程度比較 (2023 vs 2024)', fontsize=16)
ax.set_xlabel('程式語言', fontsize=14)
ax.set_ylabel('受歡迎程度評分', fontsize=14)
ax.set_xticks(x)
ax.set_xticklabels(languages, fontsize=12, rotation=0)
ax.set_ylim(0, 12)
ax.legend(fontsize=12)

# 在長條上方顯示數值
def add_labels(bars):
    for bar in bars:
        height = bar.get_height()
        ax.annotate(f'{height}',
                    xy=(bar.get_x() + bar.get_width() / 2, height),
                    xytext=(0, 3),  # 3 點的垂直偏移
                    textcoords="offset points",
                    ha='center', va='bottom',
                    fontsize=10)

add_labels(bar1)
add_labels(bar2)

# 添加網格線 (只顯示 y 軸方向)
ax.grid(True, axis='y', linestyle='--', alpha=0.7)

plt.tight_layout()
plt.savefig('programming_languages_popularity.png', dpi=300, bbox_inches='tight')
plt.show()

練習 3:多圖表組合

分析某公司的月銷售額和客戶數量關係。

import matplotlib.pyplot as plt
import numpy as np

# 模擬數據:2023 年某公司的月銷售額和客戶數量
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
sales = [120, 135, 125, 150, 165, 180, 190, 200, 210, 190, 170, 230]  # 單位:萬元
customers = [1500, 1600, 1550, 1750, 1900, 2100, 2200, 2350, 2450, 2300, 2150, 2600]  # 客戶數

# 創建一個圖表,包含兩個 y 軸
fig, ax1 = plt.subplots(figsize=(12, 7))

# 設置第一個 y 軸 (銷售額)
color = 'tab:blue'
ax1.set_xlabel('月份', fontsize=12)
ax1.set_ylabel('銷售額 (萬元)', color=color, fontsize=12)
line1 = ax1.plot(months, sales, marker='o', color=color, linewidth=2, label='銷售額')
ax1.tick_params(axis='y', labelcolor=color)
ax1.grid(True, linestyle='--', alpha=0.3)

# 創建第二個 y 軸 (客戶數)
ax2 = ax1.twinx()
color = 'tab:red'
ax2.set_ylabel('客戶數', color=color, fontsize=12)
line2 = ax2.plot(months, customers, marker='s', color=color, linewidth=2, label='客戶數')
ax2.tick_params(axis='y', labelcolor=color)

# 添加標題
plt.title('2023 年月銷售額和客戶數量趨勢', fontsize=15)

# 合併兩個圖例
lines = line1 + line2
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper left', fontsize=12)

# 在銷售額達到峰值的月份添加註釋
max_sales_idx = np.argmax(sales)
max_sales = sales[max_sales_idx]
max_sales_month = months[max_sales_idx]

ax1.annotate(f'銷售峰值: {max_sales}萬元',
            xy=(max_sales_idx, max_sales),
            xytext=(max_sales_idx-2, max_sales+15),
            arrowprops=dict(facecolor='black', shrink=0.05, width=1),
            fontsize=10)

plt.tight_layout()
plt.savefig('sales_customers_trend.png', dpi=300, bbox_inches='tight')
plt.show()

4. Plotly 基礎

核心優勢

Plotly 的主要優勢在於:

  1. 互動性:用戶可以縮放、懸停查看資訊、選擇數據點等
  2. 美觀性:默認設計精美,顏色方案專業
  3. Web 整合:適合嵌入網頁和儀表板
  4. 簡單語法:特別是 Plotly Express 的高級 API 非常簡潔

Plotly 基本結構

Plotly 的圖表由 Figure 對象組成,通常使用 Pandas DataFrame 作為數據輸入。

import plotly.express as px
import pandas as pd

# 創建一個簡單的 DataFrame
df = pd.DataFrame({
    'x': [1, 2, 3, 4, 5],
    'y': [1, 4, 9, 16, 25]
})

# 使用 Plotly Express 創建圖表
fig = px.line(df, x='x', y='y', title='簡單的線圖')

# 顯示圖表
fig.show()

Plotly 繪圖流程

使用 Plotly Express 繪圖的基本流程如下:

import plotly.express as px
import pandas as pd
import numpy as np

# 準備數據
x = np.linspace(0, 10, 100)
y = np.sin(x)

df = pd.DataFrame({'x': x, 'y': y})

# 創建圖表
fig = px.line(df, x='x', y='y', title='Sine Function')

# 更新圖表佈局(可選)
fig.update_layout(
    xaxis_title='X Axis',
    yaxis_title='Sin(x)',
    template='plotly_white'  # 使用白色主題
)

# 更新軌跡(可選)
fig.update_traces(line=dict(color='red', width=2))

# 顯示圖表
fig.show()

與 Matplotlib 的區別

  1. 互動性:Plotly 提供互動式圖表,Matplotlib 主要是靜態圖表
  2. 數據輸入:Plotly 通常使用 Pandas DataFrame,Matplotlib 更靈活,可使用各種數據結構
  3. 輸出格式:Plotly 主要針對 Web 和儀表板,Matplotlib 適合用於學術出版物和報告
  4. 語法風格:Plotly Express 有更簡潔的高級 API,Matplotlib 更詳細和可控
  5. 渲染方式:Plotly 在瀏覽器中渲染,Matplotlib 在 Python 環境中渲染

常見的 Plotly 圖表類型

線圖 (Line Plot)

import plotly.express as px
import pandas as pd
import numpy as np

# 準備數據
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)

df = pd.DataFrame({
    'x': np.concatenate([x, x]),
    'y': np.concatenate([y1, y2]),
    'function': ['sin(x)'] * 100 + ['cos(x)'] * 100
})

# 創建圖表
fig = px.line(df, x='x', y='y', color='function', title='Sin and Cos Functions',
              labels={'x': 'X Value', 'y': 'Y Value', 'function': 'Function Type'},
              line_dash='function')  # 使用不同的線型

# 更新佈局
fig.update_layout(
    template='plotly_white',
    xaxis_title='X',
    yaxis_title='Y',
    legend_title='Function Type'
)

fig.show()

散點圖 (Scatter Plot)

import plotly.express as px
import numpy as np
import pandas as pd

# 生成隨機數據
np.random.seed(42)
n_points = 100
x = np.random.randn(n_points)
y = 2 * x + np.random.randn(n_points) * 1.5
sizes = np.random.randint(5, 25, n_points)
categories = np.random.choice(['A', 'B', 'C', 'D'], n_points)

df = pd.DataFrame({
    'x': x,
    'y': y,
    'size': sizes,
    'category': categories,
    'value': np.abs(x * y)
})

# 創建散點圖
fig = px.scatter(df, x='x', y='y', 
                 color='category',  # 按類別著色
                 size='size',       # 按大小變化
                 hover_data=['value'],  # 懸停時顯示的附加數據
                 title='Interactive Scatter Plot',
                 labels={'x': 'X Axis', 'y': 'Y Axis', 'category': 'Category'},
                 color_discrete_sequence=px.colors.qualitative.Plotly)  # 使用 Plotly 顏色序列

# 添加趨勢線
fig.update_layout(
    template='plotly_white',
    legend_title='Category'
)

fig.show()

長條圖 (Bar Plot)

import plotly.express as px
import pandas as pd

# 準備數據
data = {
    'Country': ['USA', 'China', 'Japan', 'Germany', 'India', 'UK', 'France', 'Italy'],
    'GDP_2022': [25035, 18321, 4301, 4031, 3198, 3176, 2782, 2058],  # 單位:十億美元
    'GDP_2023': [26854, 17886, 4410, 4456, 3332, 3736, 2925, 2169]
}

df = pd.DataFrame(data)

# 長數據轉換
df_long = pd.melt(df, id_vars=['Country'], 
                  value_vars=['GDP_2022', 'GDP_2023'],
                  var_name='Year', value_name='GDP')

# 提取年份
df_long['Year'] = df_long['Year'].str.replace('GDP_', '')

# 創建分組長條圖
fig = px.bar(df_long, x='Country', y='GDP', color='Year', barmode='group',
             title='各國 GDP 比較 (2022-2023)',
             labels={'GDP': 'GDP (十億美元)', 'Country': '國家', 'Year': '年份'},
             hover_data=['Country', 'Year', 'GDP'],
             color_discrete_sequence=['#1f77b4', '#ff7f0e'])

# 更新佈局
fig.update_layout(
    template='plotly_white',
    legend_title='年份',
    xaxis_title='國家',
    yaxis_title='GDP (十億美元)',
    xaxis={'categoryorder':'total descending'}  # 根據總 GDP 降序排列國家
)

fig.show()

Plotly 參考資源

Plotly 動手練習範例

練習 1:互動式股票價格趨勢圖

模擬股票歷史價格數據並創建互動式趨勢圖。

import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# 生成模擬股票數據
np.random.seed(42)
n_days = 252  # 約一年的交易日

# 開始日期:今年初
start_date = datetime(2023, 1, 1)
date_list = [start_date + timedelta(days=i) for i in range(n_days)]

# 模擬價格走勢
def generate_price_series(start_price, volatility):
    prices = [start_price]
    for i in range(n_days - 1):
        change = np.random.normal(0, volatility)
        # 添加一些趨勢和季節性
        trend = 0.05 * np.sin(i / 40)  # 長期趨勢
        seasonal = 0.02 * np.sin(i / 10)  # 短期波動
        new_price = prices[-1] * (1 + change/100 + trend + seasonal)
        prices.append(max(new_price, 0.1 * start_price))  # 確保價格不會太低
    return prices

# 生成三支股票的價格數據
stocks = {
    'TECH': generate_price_series(150, 2.0),
    'BANK': generate_price_series(80, 1.2),
    'RETAIL': generate_price_series(50, 1.8)
}

# 創建數據框
df_stocks = pd.DataFrame({
    'Date': date_list * 3,
    'Price': np.concatenate([stocks['TECH'], stocks['BANK'], stocks['RETAIL']]),
    'Stock': ['TECH'] * n_days + ['BANK'] * n_days + ['RETAIL'] * n_days
})

# 計算每日價格變化
df_pivot = df_stocks.pivot(index='Date', columns='Stock', values='Price')
daily_returns = df_pivot.pct_change().dropna()

# 創建互動式圖表
fig = px.line(df_stocks, x='Date', y='Price', color='Stock',
              title='股票價格趨勢 (2023年)',
              labels={'Price': '股價 (USD)', 'Date': '日期', 'Stock': '股票'},
              hover_data={'Price': ':.2f'},  # 格式化價格顯示
              color_discrete_sequence=['#2ca02c', '#1f77b4', '#ff7f0e'])

# 增加移動平均線 (30日)
for stock in stocks.keys():
    df_stock = df_stocks[df_stocks['Stock'] == stock]
    ma30 = df_stock['Price'].rolling(window=30).mean()
    fig.add_trace(
        go.Scatter(
            x=df_stock['Date'],
            y=ma30,
            mode='lines',
            line=dict(width=1, dash='dash'),
            name=f'{stock} 30日均線',
            hoverinfo='skip'
        )
    )

# 更新佈局和互動選項
fig.update_layout(
    template='plotly_white',
    coloraxis_colorbar=dict(title='GDP (十億美元)'),
    height=600,
    margin={"r": 0, "t": 50, "l": 0, "b": 0}
)

# 添加範圍選擇器
fig.update_xaxes(
    rangeslider_visible=True,
    rangeselector=dict(
        buttons=list([
            dict(count=1, label="1月", step="month", stepmode="backward"),
            dict(count=3, label="3月", step="month", stepmode="backward"),
            dict(count=6, label="6月", step="month", stepmode="backward"),
            dict(step="all", label="全部")
        ])
    )
)

fig.show()

練習 2:互動式儀表板 - 銷售數據分析

創建一個包含多個圖表的銷售數據分析儀表板。

import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
import numpy as np

# 生成模擬銷售數據
np.random.seed(42)

# 產品類別和區域
categories = ['電子產品', '家居用品', '服裝', '食品', '書籍']
regions = ['北部', '中部', '南部', '東部']
months = ['1月', '2月', '3月', '4月', '5月', '6月', 
          '7月', '8月', '9月', '10月', '11月', '12月']

# 生成銷售數據
n_records = 1000
data = {
    '日期': pd.date_range(start='2023-01-01', end='2023-12-31', periods=n_records),
    '產品類別': np.random.choice(categories, n_records),
    '區域': np.random.choice(regions, n_records),
    '銷售額': np.random.randint(100, 10000, n_records),
    '數量': np.random.randint(1, 50, n_records)
}

df = pd.DataFrame(data)

# 添加月份列
df['月份'] = df['日期'].dt.strftime('%m月')
df['月份數字'] = df['日期'].dt.month

# 1. 創建一個包含多個子圖的儀表板
fig = make_subplots(
    rows=2, cols=2,
    subplot_titles=('各產品類別銷售分布', '各區域銷售額比較', '月度銷售趨勢', '產品類別和區域銷售熱圖'),
    specs=[[{"type": "pie"}, {"type": "bar"}],
           [{"type": "scatter"}, {"type": "heatmap"}]],
    vertical_spacing=0.1,
    horizontal_spacing=0.1
)

# 2. 子圖1:產品類別銷售額分布 (圓餅圖)
category_sales = df.groupby('產品類別')['銷售額'].sum().reset_index()
fig.add_trace(
    go.Pie(
        labels=category_sales['產品類別'], 
        values=category_sales['銷售額'],
        textinfo='percent+label',
        hole=0.4,
        marker=dict(colors=px.colors.qualitative.Plotly)
    ),
    row=1, col=1
)

# 3. 子圖2:各區域銷售額比較 (長條圖)
region_sales = df.groupby('區域')['銷售額'].sum().reset_index()
fig.add_trace(
    go.Bar(
        x=region_sales['區域'],
        y=region_sales['銷售額'],
        text=region_sales['銷售額'],
        textposition='auto',
        marker_color='rgb(158,202,225)'
    ),
    row=1, col=2
)

# 4. 子圖3:月度銷售趨勢 (線圖)
monthly_sales = df.groupby('月份數字')['銷售額'].sum().reset_index()
monthly_sales = monthly_sales.sort_values('月份數字')
monthly_sales['月份'] = [months[i-1] for i in monthly_sales['月份數字']]

fig.add_trace(
    go.Scatter(
        x=monthly_sales['月份'],
        y=monthly_sales['銷售額'],
        mode='lines+markers',
        line=dict(width=3, color='rgb(0,128,0)'),
        marker=dict(size=8, symbol='circle')
    ),
    row=2, col=1
)

# 5. 子圖4:產品類別和區域銷售熱圖
heatmap_data = df.pivot_table(
    values='銷售額', 
    index='產品類別', 
    columns='區域', 
    aggfunc='sum'
)

fig.add_trace(
    go.Heatmap(
        z=heatmap_data.values,
        x=heatmap_data.columns,
        y=heatmap_data.index,
        colorscale='Viridis',
        showscale=True
    ),
    row=2, col=2
)

# 更新佈局
fig.update_layout(
    title_text='2023年銷售數據分析儀表板',
    height=800,
    width=1000,
    showlegend=False,
    template='plotly_white'
)

# 更新子圖軸標題
fig.update_yaxes(title_text='銷售額', row=1, col=2)
fig.update_yaxes(title_text='銷售額', row=2, col=1)
fig.update_xaxes(title_text='月份', row=2, col=1)
fig.update_xaxes(title_text='區域', row=2, col=2)

fig.show()

練習 3:互動式地圖 - 全球數據視覺化

展示如何使用 Plotly 創建互動式地圖視覺化。

import plotly.express as px
import pandas as pd
import numpy as np

# 創建模擬全球 GDP 和人口數據
countries = ['USA', 'China', 'Japan', 'Germany', 'India', 'UK', 'France', 'Italy', 'Brazil', 
             'Canada', 'Russia', 'South Korea', 'Australia', 'Spain', 'Mexico', 'Indonesia',
             'Netherlands', 'Switzerland', 'Turkey', 'Saudi Arabia']

# 模擬 GDP 數據 (單位:十億美元)
gdp_values = [25000, 18000, 4300, 4100, 3500, 3200, 2800, 2000, 1800, 1700, 
              1600, 1700, 1500, 1400, 1300, 1200, 900, 800, 700, 800]

# 模擬人口數據 (單位:百萬)
population_values = [330, 1400, 126, 83, 1380, 67, 65, 60, 212, 38, 
                    144, 52, 25, 47, 128, 270, 17, 8.5, 84, 35]

# 模擬 GDP 成長率 (%)
growth_values = [2.3, 5.5, 1.0, 1.5, 7.0, 1.1, 1.4, 0.8, 3.2, 1.9, 
                1.3, 2.8, 2.2, 1.2, 2.0, 5.0, 2.3, 3.0, 4.8, 1.8]

# 模擬人均 GDP (USD)
gdp_per_capita = [np.round(g * 1000 / p, 2) for g, p in zip(gdp_values, population_values)]

# 創建 DataFrame
df_world = pd.DataFrame({
    'country': countries,
    'GDP (Billion USD)': gdp_values,
    'Population (Million)': population_values,
    'GDP Growth (%)': growth_values,
    'GDP per Capita (USD)': gdp_per_capita
})

# 添加 ISO 國家代碼 (用於地圖)
iso_codes = ['USA', 'CHN', 'JPN', 'DEU', 'IND', 'GBR', 'FRA', 'ITA', 'BRA', 
             'CAN', 'RUS', 'KOR', 'AUS', 'ESP', 'MEX', 'IDN', 'NLD', 'CHE', 'TUR', 'SAU']
df_world['iso_alpha'] = iso_codes

# 創建互動式世界地圖
fig = px.choropleth(df_world,
                    locations='iso_alpha',
                    color='GDP (Billion USD)',
                    hover_name='country',
                    hover_data=['GDP (Billion USD)', 'Population (Million)', 
                               'GDP Growth (%)', 'GDP per Capita (USD)'],
                    color_continuous_scale=px.colors.sequential.Plasma,
                    projection='natural earth',
                    title='2023年全球GDP分布')

# 更新佈局
fig.update_layout(
    template='plotly_white',
    coloraxis_colorbar=dict(title='GDP (十億美元)'),
    height=600,
    margin={"r": 0, "t": 50, "l": 0, "b": 0}
)

fig.show()

# 創建氣泡地圖 (顯示 GDP、人口和成長率)
fig_bubble = px.scatter_geo(df_world, 
                           locations='iso_alpha',
                           color='GDP Growth (%)',
                           size='GDP (Billion USD)',
                           hover_name='country',
                           hover_data=['GDP (Billion USD)', 'Population (Million)', 
                                      'GDP Growth (%)', 'GDP per Capita (USD)'],
                           color_continuous_scale=px.colors.sequential.Viridis,
                           projection='natural earth',
                           title='全球經濟指標氣泡圖 (大小=GDP, 顏色=GDP成長率)')

# 更新佈局
fig_bubble.update_layout(
    template='plotly_white',
    height=600,
    margin={"r": 0, "t": 50, "l": 0, "b": 0}
)

fig_bubble.show()

結合 Matplotlib 和 Plotly 的優勢

在實際應用中,可以根據需求靈活選擇使用 Matplotlib 或 Plotly:

何時使用 Matplotlib

  • 生成靜態報告和學術論文圖表
  • 需要高度精確控制圖表外觀
  • 需要與 NumPy 生態系統緊密整合
  • 處理大量數據時 (Matplotlib 通常比 Plotly 更高效)
  • 需要將圖表保存為高質量圖像文件
# Matplotlib 範例:論文品質圖表
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import MultipleLocator

# 設置風格
plt.style.use('seaborn-whitegrid')

# 創建數據
x = np.linspace(0, 10, 100)
y1 = 4 + 2 * np.sin(2 * x)
y2 = 4 + 2 * np.sin(2 * x + np.pi/4)
y3 = 4 + 2 * np.sin(2 * x + np.pi/2)

# 創建圖表和軸
fig, ax = plt.subplots(figsize=(10, 6))

# 繪製三條線
ax.plot(x, y1, 'o-', color='#1f77b4', markersize=4, linewidth=1.5, markevery=10, label='Phase 0')
ax.plot(x, y2, 's-', color='#ff7f0e', markersize=4, linewidth=1.5, markevery=10, label='Phase π/4')
ax.plot(x, y3, '^-', color='#2ca02c', markersize=4, linewidth=1.5, markevery=10, label='Phase π/2')

# 添加網格
ax.grid(True, linestyle='--', alpha=0.7)

# 設置坐標軸
ax.set_xlim(0, 10)
ax.set_ylim(0, 8)
ax.xaxis.set_major_locator(MultipleLocator(1))
ax.yaxis.set_major_locator(MultipleLocator(1))

# 添加標題和標籤
ax.set_title('Shifted Sine Waves', fontsize=16, pad=15)
ax.set_xlabel('x', fontsize=14, labelpad=10)
ax.set_ylabel('y = 4 + 2·sin(2x + φ)', fontsize=14, labelpad=10)
ax.legend(fontsize=12, frameon=True)

# 添加註釋
ax.annotate('Amplitude = 2', xy=(2, 6), xytext=(3, 7),
            arrowprops=dict(facecolor='black', shrink=0.05, width=1.5),
            fontsize=12)

# 調整佈局並保存
plt.tight_layout()
plt.savefig('publication_quality_figure.png', dpi=300, bbox_inches='tight')
plt.savefig('publication_quality_figure.pdf', bbox_inches='tight')
plt.show()

何時使用 Plotly

  • 創建互動式儀表板
  • 需要用戶能夠探索數據
  • 嵌入到網頁或 Jupyter Notebook 中
  • 需要分享給不熟悉 Python 的使用者
  • 需要展示多維數據並讓用戶自行發掘洞見
# Plotly 範例:互動式儀表板元素
import plotly.graph_objects as go
import plotly.express as px
import pandas as pd
import numpy as np

# 創建模擬股票數據
np.random.seed(42)
dates = pd.date_range('2023-01-01', '2023-12-31', freq='B')  # 工作日
prices = 100 * (1 + np.cumsum(np.random.normal(0, 0.015, len(dates))))
volumes = np.random.randint(100000, 1000000, len(dates))

# 創建 DataFrame
df = pd.DataFrame({
    'Date': dates,
    'Price': prices,
    'Volume': volumes
})

# 計算移動平均線
df['MA5'] = df['Price'].rolling(window=5).mean()
df['MA20'] = df['Price'].rolling(window=20).mean()

# 創建燭線圖
fig = go.Figure()

# 添加價格線
fig.add_trace(
    go.Scatter(
        x=df['Date'],
        y=df['Price'],
        mode='lines',
        name='股價',
        line=dict(color='rgba(0,0,0,0.5)')
    )
)

# 添加移動平均線
fig.add_trace(
    go.Scatter(
        x=df['Date'],
        y=df['MA5'],
        mode='lines',
        name='5日均線',
        line=dict(color='rgba(255,0,0,0.8)', width=1)
    )
)

fig.add_trace(
    go.Scatter(
        x=df['Date'],
        y=df['MA20'],
        mode='lines',
        name='20日均線',
        line=dict(color='rgba(0,0,255,0.8)', width=1.5)
    )
)

# 添加成交量長條圖 (使用 subplots)
fig.add_trace(
    go.Bar(
        x=df['Date'],
        y=df['Volume'],
        name='成交量',
        marker=dict(color='rgba(0,128,0,0.5)'),
        opacity=0.8,
        yaxis='y2'
    )
)

# 更新佈局
fig.update_layout(
    title='2023年股票價格和成交量分析',
    yaxis=dict(
        title='股價',
        titlefont=dict(color='rgba(0,0,0,0.8)'),
        tickfont=dict(color='rgba(0,0,0,0.8)')
    ),
    yaxis2=dict(
        title='成交量',
        titlefont=dict(color='green'),
        tickfont=dict(color='green'),
        anchor='x',
        overlaying='y',
        side='right'
    ),
    xaxis=dict(
        title='日期',
        rangeslider=dict(visible=True),
        rangeselector=dict(
            buttons=list([
                dict(count=1, label='1月', step='month', stepmode='backward'),
                dict(count=3, label='3月', step='month', stepmode='backward'),
                dict(count=6, label='6月', step='month', stepmode='backward'),
                dict(step='all', label='全部')
            ])
        )
    ),
    template='plotly_white',
    hovermode='x unified',
    legend=dict(orientation='h', y=1.02)
)

fig.show()

結論:選擇適合您需求的工具

沒有一個「最好」的可視化工具,而是要根據您的具體需求選擇合適的工具:

  1. 目標受眾是誰?

    • 學術/研究人員 → Matplotlib
    • 商業分析/網頁使用者 → Plotly
  2. 呈現方式是什麼?

    • 報告/論文/出版物 → Matplotlib
    • 網頁/儀表板/互動式演示 → Plotly
  3. 數據的複雜度如何?

    • 需要精確控制的複雜圖表 → Matplotlib
    • 多維數據的互動式探索 → Plotly
  4. 開發時間有多少?

    • 需要快速生成標準圖表 → Plotly Express
    • 有時間進行詳細客製化 → Matplotlib
  5. 誰會使用這些圖表?

    • 僅供個人或團隊使用 → Matplotlib 或 Plotly 均可
    • 需要分享給不同技術背景的人 → Plotly

最佳實踐建議

  1. 從問題開始思考:先確定您要回答什麼問題,再選擇適合的視覺化工具
  2. 保持簡單:不要過度裝飾您的圖表,讓數據說話
  3. 考慮色彩無障礙:選擇色盲友好的調色板
  4. 註釋關鍵點:突出顯示重要的數據點或趨勢
  5. 適當使用互動性:互動功能應該增強而不是分散對數據的理解

無論您選擇哪種工具,記住:好的資料視覺化不是關於炫技,而是關於有效地傳達數據中的見解和故事。

祝您在 Python 資料視覺化的旅程中取得成功!