## 1. 這篇在解決什麼問題?
這篇主要在解釋 `is_meaningful_table()` 裡面的判斷邏輯:
def is_meaningful_table(table_df: pd.DataFrame) -> bool:
# Filter out empty tables and tiny fragments detected by PyMuPDF.
# After normalize_table_df(), blank cells are NA, so notna() counts real content cells.
if table_df.empty:
return False
# table_df.notna() gives a True/False grid; first sum counts non-NA cells per column,
# second sum adds all columns into one total content-cell count.
non_empty_cells = int(table_df.notna().sum().sum())
return non_empty_cells >= 3它的責任不是清理表格,
而是判斷清理後的表格是否值得輸出。
判斷規則很簡單:
如果 table_df 是空表格,回傳 False。
如果真正有內容的 cell 數量少於 3,回傳 False。
否則回傳 True。不過 `is_meaningful_table()` 能正確判斷,
有一個前提:傳進來的 `table_df` 必須已經先被清理過。
這個前置清理工作由 `normalize_table_df()` 負責。
它會把 PyMuPDF 抓到的表格整理成
比較適合 pandas 判斷的 DataFrame:
去掉文字前後空白。
把空字串 "" 轉成 pd.NA。
刪掉全空 row / column。在 `my_pdf2text_08.py` 裡,PyMuPDF 抓到表格後,
會先轉成 pandas `DataFrame`,
再交給 `normalize_table_df()` 清理:
table_df = normalize_table_df(pd.DataFrame(extracted_table))接著才用 `is_meaningful_table()`
判斷這張表格是否值得輸出:
if not is_meaningful_table(table_df):
print(f"[INFO] Page {page_number}: skip low-information table fragment #{table_index}")
continue目標是過濾掉 PyMuPDF 偵測到的
空表格、殘缺表格、低資訊表格碎片。
核心問題是:
dropna() 不會刪掉空字串 ""。
notna() 也會把空字串 "" 當成 True。所以如果 PDF 表格裡有很多 `””` 或 `” “`,
直接用 `dropna()` / `notna()` 會誤判。
## 2. 先建立示範 DataFrame
在 Jupyter / Interactive Window 裡先執行:
import pandas as pd
raw_df = pd.DataFrame([
["Part No", "Value", ""],
["R1", "10k", ""],
["", "", ""],
["C1", "0.1uF", ""],
[" ", " ", ""],
])
raw_df這張表看起來有很多空白格,
但那些空白格其實是空字串或空白字串:
""
" "它們不是 pandas 的缺值。
## 3. 為什麼 raw_df.notna() 全部都是 True?
執行:
“`python
raw_df.notna()
“`
你會看到全部 cell 都是 `True`。
原因是 pandas 認為下面這些是「有值」:
pd.notna("")
pd.notna(" ")結果都是:True
因為 `””` 和 `” “` 都是字串物件,不是缺值。
pandas 常見的缺值才是:
pd.NA
None
float("nan")所以如果直接算:
raw_df.notna().sum()會得到每一欄都是 5,
因為每個 cell 都被當成非缺值。
再算:
raw_df.notna().sum().sum()會得到:
但這不是我們想要的「真正有內容的 cell 數」。
## 4. dropna() 也不會刪掉空字串
直接試:
raw_df.dropna(axis=0, how="all")你會發現看起來空白的 row 沒有被刪掉。
原因一樣:
dropna() 只處理 NA / NaN。
dropna() 不會把 "" 當成 NA。所以這一列:
["", "", ""]在 pandas 眼中不是全 NA,而是三個空字串。
這一列:
[" ", " ", ""]也不是全 NA,而是三個字串。
## 5. 正確清理步驟一:先 strip 字串
先複製一份:
normalized_df = raw_df.copy()然後把字串前後空白拿掉:
normalized_df = normalized_df.map(
lambda value: value.strip() if isinstance(value, str) else value
)
normalized_df這一步會把:
“`python
” “
“`
變成:
“`python
“”
“`
也就是先把「只有空白的字串」統一整理成空字串。
## 6. 正確清理步驟二:把空字串轉成 pd.NA
接著執行:
normalized_df = normalized_df.replace("", pd.NA)
normalized_df這一步才是真正關鍵。
因為做完之後,原本的空字串會變成
pandas 認得的缺值(“” -> <NA>):
現在再看:
normalized_df.notna()只有真正有文字的 cell 會是 `True`,空格會變成 `False`。
## 7. 正確清理步驟三:刪掉全空 row / column
補充一個容易混淆的點:
“” 和 pd.NA 輸出到 Excel 後,看起來通常都是空白格。
但它們在 pandas 裡的語意不同:
所以把 `””` 轉成 `pd.NA` 不是為了 Excel 顯示,
而是為了讓 pandas 後面的判斷正確。
刪掉整列都是 NA 的 row:
normalized_df = normalized_df.dropna(axis=0, how="all")
# axis=0 == axis="index"
normalized_df刪掉整欄都是 NA 的 column:
normalized_df = normalized_df.dropna(axis=1, how="all")
# axis=1 == axis="columns"
normalized_df最後重設 index:
normalized_df = normalized_df.reset_index(drop=True)
normalized_df這就是 `my_pdf2text_08.py` 裡的:
def normalize_table_df(table_df: pd.DataFrame) -> pd.DataFrame:
normalized_df = table_df.copy()
normalized_df = normalized_df.map(
lambda value: value.strip() if isinstance(value, str) else value
)
normalized_df = normalized_df.replace("", pd.NA)
normalized_df = normalized_df.dropna(axis=0, how="all")
normalized_df = normalized_df.dropna(axis=1, how="all")
return normalized_df.reset_index(drop=True)## 8. 驗證 notna().sum().sum()
清理後執行:
normalized_df.notna()再看每欄有幾個非 NA cell:
normalized_df.notna().sum()這裡的第一個 `.sum()` 是沿著 column 統計:
每一欄各自有幾個 True
再執行:
normalized_df.notna().sum().sum()第二個 `.sum()` 是把每一欄的數量再加總:
整張表格總共有幾個真正有內容的 cell
所以:
int(normalized_df.notna().sum().sum())意思是:
把整張表格中非 NA 的 cell 數量算成一個 int。
## 9. 驗證 is_meaningful_table()
目前程式用這個函式判斷表格是否值得輸出:
def is_meaningful_table(table_df: pd.DataFrame) -> bool:
if table_df.empty:
return False
non_empty_cells = int(table_df.notna().sum().sum())
return non_empty_cells >= 3可以在 Jupyter 裡加上 `print()` 觀察:
def is_meaningful_table_demo(table_df: pd.DataFrame) -> bool:
if table_df.empty:
print("empty table")
return False
non_empty_cells = int(table_df.notna().sum().sum())
print("non_empty_cells =", non_empty_cells)
return non_empty_cells >= 3
is_meaningful_table_demo(normalized_df)如果清理後還有至少 3 個有內容的 cell,就會回傳:True
如果只有 1、2 個 cell 有內容,
就當成 PyMuPDF 偵測到的低資訊碎片,跳過不輸出。
## 10. 一次跑完整流程
import pandas as pd
raw_df = pd.DataFrame([
["Part No", "Value", ""],
["R1", "10k", ""],
["", "", ""],
["C1", "0.1uF", ""],
[" ", " ", ""],
])
def normalize_table_df_demo(table_df: pd.DataFrame) -> pd.DataFrame:
normalized_df = table_df.copy()
normalized_df = normalized_df.map(
lambda value: value.strip() if isinstance(value, str) else value
)
normalized_df = normalized_df.replace("", pd.NA)
normalized_df = normalized_df.dropna(axis=0, how="all")
normalized_df = normalized_df.dropna(axis=1, how="all")
return normalized_df.reset_index(drop=True)
def is_meaningful_table_demo(table_df: pd.DataFrame) -> bool:
if table_df.empty:
return False
non_empty_cells = int(table_df.notna().sum().sum())
print("non_empty_cells =", non_empty_cells)
return non_empty_cells >= 3
clean_df = normalize_table_df_demo(raw_df)
display(raw_df)
display(raw_df.notna())
display(clean_df)
display(clean_df.notna())
is_meaningful_table_demo(clean_df)## 11. 結論
這段流程的重點是:
PyMuPDF 抽出的表格空格常常是 “”,不是 NA。
dropna() 不會處理 “”。
notna() 會把 “” 當 True。
所以要先 strip,再 replace(“”, pd.NA)。
之後 dropna() 和 notna().sum().sum() 才會得到合理結果。
推薦hahow線上學習python: https://igrape.net/30afN