在資料科學領域,資料清理和視覺化是不可或缺的環節。資料清理確保資料品質,而視覺化則幫助我們理解資料的分佈和趨勢。本文將示範如何使用 Python 的 pandas 和 Seaborn 函式庫進行資料清理和視覺化。首先,我們會探討如何處理缺失值,包含識別、分析和補充缺失值的策略。接著,我們將使用 Matplotlib 和 Seaborn 建立各種圖表,例如直方圖、KDE 圖、箱型圖、小提琴圖和蜂群圖,以視覺化資料的分佈和特徵。最後,我們將探討 pandas 的資料重塑功能,包含串接、合併和樞紐分析等技巧,讓讀者能有效地整理和分析資料。這些技巧對於從資料中提取有價值的洞察至關重要,也是資料科學家必備的技能。

資料清理與視覺化分析

在進行資料分析時,資料清理是至關重要的一步。資料清理的目的是確保資料的完整性和準確性,以便進行有效的分析。本章節將討論如何清理和視覺化資料。

處理缺失值

在資料集中,缺失值是常見的問題。缺失值可能會導致分析結果的偏差或錯誤。因此,處理缺失值是資料清理的重要步驟。

檢查缺失值

首先,我們需要檢查資料集中哪些欄位存在缺失值。可以使用 pd.isna() 函式來檢查缺失值。

df.select_dtypes(exclude=["string"]).pipe(pd.isna).sum().sort_values(ascending=False).head()

分析缺失值

檢查結果顯示,「cylinders」 和 「displ」 欄位存在較多的缺失值。進一步分析發現,這些缺失值主要對應於電動車輛。

df.loc[df["cylinders"].isna(), ["make", "model"]].value_counts()

補充缺失值

對於 「cylinders」 欄位,由於它是一個類別型變數,將缺失值補充為 0 是合理的。

df["cylinders"] = df["cylinders"].fillna(0)

然而,對於 「displ」 欄位,由於它是一個連續型變數,補充缺失值為 0 可能會導致平均值的偏差。因此,決定保留這些缺失值。

資料視覺化

視覺化是理解資料分佈和特徵的重要工具。本文將介紹如何使用直方圖和 KDE 圖來視覺化連續型資料。

直方圖

直方圖是一種常見的視覺化工具,用於展示資料的分佈。

df["city08"].plot(kind="hist", bins=30)

KDE 圖

KDE(Kernel Density Estimation)圖是一種平滑的直方圖,可以更好地展示資料的分佈。

fig, axes = plt.subplots(nrows=2, ncols=1)
axes[0].set_xlim(0, 40)
axes[1].set_xlim(0, 40)
axes[0].set_ylabel("city")
axes[1].set_ylabel("highway")
df["city08"].plot(kind="kde", ax=axes[0])
df["highway08"].plot(kind="kde", ax=axes[1])

使用 Seaborn 進行進階視覺化

Seaborn 是一個根據 Matplotlib 的視覺化函式庫,提供了更多進階的視覺化功能。

條形圖

import seaborn as sns
sns.set_theme()
sns.set_style("white")

df = pd.DataFrame([
    ["Q1-2024", "project_a", 1],
    ["Q1-2024", "project_b", 1],
    ["Q2-2024", "project_a", 2],
    ["Q2-2024", "project_b", 2],
    ["Q3-2024", "project_a", 4],
    ["Q3-2024", "project_b", 3],
    ["Q4-2024", "project_a", 8],
    ["Q4-2024", "project_b", 4],
    ["Q5-2025", "project_a", 16],
    ["Q5-2025", "project_b", 5],
], columns=["quarter", "project", "github_stars"])

sns.barplot(df, x="quarter", y="github_stars", hue="project")

線圖

sns.lineplot(df, x="quarter", y="github_stars", hue="project")

圖表翻譯:

此條形圖與線圖用於比較兩個 GitHub 專案在不同季度獲得的星數。從圖中可以看出,project_a 的星數增長速度明顯快於 project_b

資料視覺化:使用 Seaborn 分析電影評分趨勢

在資料視覺化的過程中,Seaborn 提供了一系列強大的工具,能夠幫助我們更好地理解資料的分佈和趨勢。本文將介紹如何使用 Seaborn 的不同視覺化方法來分析電影評分的變化。

資料準備

首先,我們需要準備好要分析的資料。在這個範例中,我們使用了包含電影標題、發行年份、IMDB 評分和內容分級的電影資料集。為了進行分析,我們需要對資料進行一些清理工作,例如將發行年份轉換為整數型別,並根據發行年份將電影劃分為不同的年代。

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

# 讀取資料
df = pd.read_csv(
    "data/movie.csv",
    usecols=["movie_title", "title_year", "imdb_score", "content_rating"],
    dtype_backend="numpy_nullable",
    dtype={"title_year": pd.Int16Dtype()},
)

# 根據發行年份劃分年代
df = df.assign(
    title_decade=lambda x: pd.cut(x["title_year"], bins=range(1910, 2021, 10))
)

程式碼解密:

  1. pd.read_csv:用於讀取 CSV 檔案中的資料,並選取需要的欄位。
  2. dtype={"title_year": pd.Int16Dtype()}:指定 title_year 欄位的資料型別為整數,以確保資料的正確性。
  3. pd.cut:根據指定的區間範圍,將 title_year 劃分為不同的年代區間,並將結果儲存在 title_decade 欄位中。

使用箱型圖視覺化評分分佈

箱型圖是一種用於展示資料分佈的強大工具,可以幫助我們觀察不同年代的電影評分變化。

sns.boxplot(
    data=df,
    x="imdb_score",
    y="title_decade",
)
plt.show()

圖表翻譯:

此箱型圖展示了不同年代的電影評分分佈。從圖中可以觀察到,中位數評分(黑線)隨著時間的推移呈現下降趨勢。此外,箱型圖的「鬍鬚」顯示了評分的離散程度,近年來的電影評分範圍似乎更廣。

使用小提琴圖進一步分析評分分佈

小提琴圖結合了箱型圖和 KDE(核密度估計)圖,可以更詳細地展示資料的分佈情況。

sns.violinplot(
    data=df,
    x="imdb_score",
    y="title_decade",
)
plt.show()

圖表翻譯:

小提琴圖顯示了不同年代電影評分的 KDE 分佈,可以觀察到某些年代的評分呈現雙峰分佈,例如 1950 年代和 1960 年代,這表明這些年代的電影評分可能存在不同的群體。

使用蜂群圖觀察評分數量變化

蜂群圖可以幫助我們觀察不同年代的評分數量,並與評分分佈結合起來進行分析。

sns.swarmplot(
    data=df,
    x="imdb_score",
    y="title_decade",
    size=.25,
)
plt.show()

圖表翻譯:

蜂群圖顯示了不同年代的電影評分數量和分佈情況。從圖中可以看出,大部分的評分集中在 1990 年代以後,尤其是 2000 年至 2010 年之間。這表明近年來的電影評論數量明顯增加,而早期的電影評論相對較少。

資料重塑的藝術:掌握 pandas 的強大功能

在資料分析的世界裡,原始資料往往並非立即可用。為了獲得有價值的洞察,我們需要對資料進行清理、轉換和重塑,使其變得可用、可理解且易於分析。pandas 函式庫提供了多種強大的工具來幫助我們完成這些任務。在本章中,我們將探討 pandas 中的資料重塑功能,包括串接、合併、樞紐分析和資料轉換等。

使用 pd.concat 進行資料串接

串接是指將多個 DataFrame 物件堆積疊在一起的過程。pandas 支援垂直串接和水平串接兩種方式。垂直串接將 DataFrame 物件堆積疊在彼此之上,而水平串接則將它們並排放置。

垂直串接

import pandas as pd

# 建立兩個 DataFrame 物件
df1 = pd.DataFrame({
    'A': ['A0', 'A1', 'A2'],
    'B': ['B0', 'B1', 'B2']
})

df2 = pd.DataFrame({
    'A': ['A3', 'A4', 'A5'],
    'B': ['B3', 'B4', 'B5']
})

# 垂直串接
result = pd.concat([df1, df2])
print(result)

#### 內容解密:

此程式碼首先建立了兩個 DataFrame 物件 df1df2,然後使用 pd.concat 將它們垂直串接在一起。輸出的結果是一個新的 DataFrame,包含了 df1df2 的所有資料。

水平串接

# 建立兩個 DataFrame 物件
df1 = pd.DataFrame({
    'A': ['A0', 'A1', 'A2'],
    'B': ['B0', 'B1', 'B2']
})

df2 = pd.DataFrame({
    'C': ['C0', 'C1', 'C2'],
    'D': ['D0', 'D1', 'D2']
})

# 水平串接
result = pd.concat([df1, df2], axis=1)
print(result)

#### 內容解密:

此程式碼使用 pd.concatdf1df2 水平串接在一起。axis=1 引數指定了串接的方向為水平方向。輸出的結果是一個新的 DataFrame,包含了 df1df2 的所有欄位。

資料合併與重塑

除了串接之外,pandas 還提供了多種方法來合併和重塑資料,包括 pd.mergepd.DataFrame.joinpd.pivot_table 等。這些方法可以幫助我們根據不同的需求對資料進行重組和分析。

使用 pd.merge 合併資料

# 建立兩個 DataFrame 物件
df1 = pd.DataFrame({
    'key': ['K0', 'K1', 'K2'],
    'A': ['A0', 'A1', 'A2']
})

df2 = pd.DataFrame({
    'key': ['K0', 'K1', 'K2'],
    'B': ['B0', 'B1', 'B2']
})

# 合併資料
result = pd.merge(df1, df2, on='key')
print(result)

#### 內容解密:

此程式碼使用 pd.mergedf1df2 根據共同的 key 欄位進行合併。輸出的結果是一個新的 DataFrame,包含了 df1df2 的相關資料。

資料重塑的高階技巧

pandas 還提供了多種高階的資料重塑技巧,包括使用 pd.DataFrame.stackpd.DataFrame.unstackpd.DataFrame.melt 等方法。這些方法可以幫助我們對資料進行更複雜的重組和分析。

使用 pd.DataFrame.pivot_table 進行樞紐分析

# 建立一個 DataFrame 物件
df = pd.DataFrame({
    'A': ['foo', 'bar', 'foo', 'bar'],
    'B': ['one', 'one', 'two', 'two'],
    'C': ['small', 'large', 'large', 'small'],
    'D': [1, 2, 3, 4]
})

# 進行樞紐分析
result = pd.pivot_table(df, values='D', index=['A', 'B'], columns=['C'])
print(result)

#### 內容解密:

此程式碼使用 pd.pivot_tabledf 進行樞紐分析,根據 AB 欄位進行分組,並計算 D 欄位在不同 C 欄位值下的平均值。輸出的結果是一個樞紐表,清晰地展示了資料的結構和關係。

資料重塑與合併:深入理解 pd.concat

在處理資料分析任務時,經常需要合併多個資料集。Pandas 提供的 pd.concat 函式是實作此任務的強大工具。本章節將探討如何使用 pd.concat 來合併 DataFrame 物件,並介紹其各種引數的使用方法。

基本使用方法

假設我們收集了不同公司在兩個不同季度內的股票表現資料。為了展示 pd.concat 的工作原理,我們故意讓兩個 DataFrame 物件涵蓋不同的時間段、公司和欄位。

import pandas as pd

# 建立第一季度的 DataFrame
df_q1 = pd.DataFrame([
    ["AAPL", 100., 50., 75.],
    ["MSFT", 80., 42., 62.],
    ["AMZN", 60., 100., 120.],
], columns=["ticker", "shares", "low", "high"])

# 資料型別轉換
df_q1 = df_q1.convert_dtypes(dtype_backend="numpy_nullable")

# 建立第二季度的 DataFrame
df_q2 = pd.DataFrame([
    ["AAPL", 80., 70., 80., 77.],
    ["MSFT", 90., 50., 60., 55.],
    ["IBM", 100., 60., 70., 64.],
    ["GE", 42., 30., 50., 44.],
], columns=["ticker", "shares", "low", "high", "close"])

# 資料型別轉換
df_q2 = df_q2.convert_dtypes(dtype_backend="numpy_nullable")

#### 內容解密:
這裡建立了兩個 DataFrame 物件分別代表第一季度和第二季度的股票資料。`convert_dtypes` 方法用於最佳化資料型別以提高運算效率

### 合併 DataFrame
最基本的 `pd.concat` 呼叫方式是將多個 DataFrame 物件放入一個列表中預設情況下這些 DataFrame 將被垂直堆積疊

```python
# 合併 DataFrame
result = pd.concat([df_q1, df_q2])
print(result)

內容解密:

  • pd.concatdf_q1df_q2 合併,由於 df_q1 缺少 close 欄位,pandas 將自動填充缺失值。
  • 索引值被保留,因此結果中的索引不是連續的。

控制索引與合併方向

可以透過 ignore_index=True 引數來重置索引。此外,使用 keys 引數可以為來源資料新增標籤。

# 重置索引並新增來源標籤
result = pd.concat([df_q1, df_q2], ignore_index=True, keys=["q1", "q2"])
print(result)

內容解密:

  • ignore_index=True 重置了索引,使其連續。
  • 使用 keys=["q1", "q2"] 為資料來源增加了標籤,但這裡的用法不正確,應直接使用 keys 於具有層級索引的情境。

控制合併方向

預設情況下,pd.concat 是垂直合併(axis=0)。可以透過設定 axis=1 來實作水平合併。

# 設定 ticker 為索引後進行水平合併
result = pd.concat([df_q1.set_index("ticker"), df_q2.set_index("ticker")], axis=1, keys=["q1", "q2"])
print(result)

內容解密:

  • ticker 設定為索引後,可以根據 ticker 對齊資料進行水平合併。
  • 由於某些 ticker 只出現在一個季度,合併結果中會出現缺失值。

控制對齊行為

預設情況下,pd.concat 執行的是「外連線」(outer join),即包含所有索引值。可以使用 join="inner" 來只保留共同的索引值。

# 只保留共同的 ticker
result = pd.concat([df_q1.set_index("ticker"), df_q2.set_index("ticker")], axis=1, keys=["q1", "q2"], join="inner")
print(result)

內容解密:

  • 使用 join="inner" 後,只保留了同時出現在兩個季度中的 ticker(AAPL 和 MSFT)。

最佳實踐:避免在迴圈中使用 pd.concat

在迴圈中重複呼叫 pd.concat 是低效的。最佳實踐是將 DataFrame 存入列表,最後一次性呼叫 pd.concat

# 正確的做法:先收集 DataFrame 到列表,最後一次性合併
dataframes = [df_q1]
for _ in range(1000):
    dataframes.append(df_q1)
result = pd.concat(dataframes)
print(f"最終 DataFrame 的形狀是 {result.shape}")

內容解密:

  • 這種做法大幅提升了效能,因為它減少了重複合併的次數。