從 Excel 到 Python

雲棲號資訊:【點擊查看更多行業資訊
在這裏您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

在 2016 年的 Build 大會上,微軟宣佈全球有 12 億人在使用 Excel,而在同一年,全球的人口爲 74 億。也就是說,使用 Excel 的人佔全球人口的 16.2%。

2019 年的一份報告( https://slashdata-website-cms.s3.amazonaws.com/sample_reports/ZAamt00SbUZKwB9j.pdf )顯示,Python 擁有 820 萬活躍開發者,佔全球人口的 0.001%。

從這些數據可以看出,增強 Excel 和 Python 之間的交互性對我們是有好處的,這爲更多人打開了一扇使用 Python 工具的大門。
Python 在 Excel 前端方面的機會是巨大的。在本文中,我們將分享如何實現一個“典型的”財務 Excel 表格。

先工具,後 Excel

在幾乎所有我能想到的場景中,通常是先寫 Python 代碼,不過必須要保持數據“輸入”格式的靈活性。

untitled

改變輸入數據集格式不應該影響到代碼
假設我們使用 Pandas 讀取一個或兩個 CSV/Excel 表格,可能會依賴一組給定的列名。
如果有數千行這樣的代碼,我們就依賴了很多硬編碼的列名,當我們試圖使用 Excel 動態輸入列名時,就會遇到問題。
因此,在最初的原型設計階段,在還沒有使用 Excel 工作表時,可以在代碼裏將列名和內部標籤名映射起來:

mappings = {'loan identifier': 'loan_id', 
                'amt': 'amount', 
                 ... 
                'init fees': 'initial_fees'} 
     
data.rename(mappings, axis=1, inplace=True) 

稍後,這種映射將被 Excel 工作表取代。

Excel 前端

等到 Python 初具模型,就可以開始構建 Excel 前端了。首先,我們要確定哪些變量可以放在 Excel 工作表中。
在開發這類工具時,一般都是要假設輸入數據的格式是會變的。

這點要麼很重要,要麼不那麼重要,具體取決於你所在的工作環境以及你要開發什麼樣的工具。有些工作流程定義得比較好,數據格式不太可能會發生變化。

但是,我總是會傾向於保持謹慎,希望通過 Excel 來增加靈活性,但要注意不要將事情複雜化。

untitled

使用 Excel 將 Python 內部列名與外部 CSV/Excel 列名映射起來
使用內部命名系統並允許 Excel 用戶指定列映射,這是保持靈活性的一個很好的例子。現在,Excel 用戶不再依賴於硬編碼的列名,他們可以在不修改 Python 代碼的情況下調整列映射。

映射

mappings 是集成的核心部分,它的內容來自 Excel 中的一張表(我通常會叫它 Mapping)。
要得到 mappings,我們需要一個函數來讀取 Excel。爲此,我們使用了 openpyxl。
我們可以這樣讀取 Excel 中的單元格:

import openpyxl 
# 加載工作簿 
wb = openpyxl.load_workbook("sheet.xlsx", data_only=True) 
# 創建工作簿對象 
ws = wb.active 
# 獲取單元格 E4 的值 
value = ws['E4'].value 

我們可以通過這種方式得到 mappings。我們將代碼稍作調整,添加 Excel 工作簿“tool_setup.xlsx”本地路徑。
我們還要假設 Excel 的當前工作表可能不是我們想要的那個,而且可能會新增、被刪除或被移動,所以我們需要通過遍歷找到目標工作表的索引位置:

# 首先,我們設置 Excel 文件的路徑 
path = r".\documents\tool_setup.xlsx" 
# 加載文件,創建工作簿對象 
wb = openpyxl.load_workbook(path, data_only=True) 
# 找到目標工作表的索引 
idx = [i for i, name in wb.sheetnames if name == 'Mapping'][0] 
# 將目標工作表設置爲當前工作表 
wb.active = idx 
ws = wb.active 

現在,我們可以填充 mappings 內容了 :

mappings = {} 
mappings['Amount'] = ws["E4"].value 
mappings['Term'] = ws["E5"].value 

保持靈活性

如果工作表裏添加了新行或者把舊行刪除,有可能會得到一個不正確的 mappings。爲了避免這種情況,我們需要 search_col 函數,它會遍歷查找每個單元格,直到找到包含我們想要的值(或超過 limit 限制)的單元格。

# 定義一個函數,用於查找 openpyxl 工作簿對象中的給定列 
def search_col(sheet, column, value, limit=100): 
   
  # 從 1 開始,逐行查找,直到達到 limit 限制 
    for row in range(1, limit+1): 
       
        if sheet[f"column{row}"].value == value: 
            # 找到想要的單元格,返回單元格的列和行 
            return (col, row) 

search_col 返回我們想要的數據的列和行。

untitled

如果沒處理好,哪怕是在工作表裏添加一個註釋也會讓工具不可用。左邊的“Internal”在第 12 行,而右邊是第 14 行。
我們可以像下面這樣找到“Internal”的單元格位置:

search_col(ws, 'B', 'Internal') 
[Out]: ('B', 12) 

接下來,我們通過循環往 mappings 添加其他列映射。在遇到兩個或者更多個空的單元格後,我們就知道映射內容已經全部讀取完畢,就可以結束循環了:

empty = 0  # 初始化空單元格數量 
while empty < 2: 
    # 增加行計數 
    row += 1 
    # 賦值 
    internal = ws[f'B{row}'].value 
     
    if internal is None: 
        empty += 1  # 遇到空單元格就增加空單元格計數 
    else: 
        # 加入 mappings 
        mappings[internal] = ws[f'D{row}'].value 
        empty = 0  # also re-initialize the empty counter 

運行上面的代碼,就可以得到像下面這樣的 mappings:

{ 
    'Loan ID': 'loan identifier', 
    'Product': 'product type', 
     ... 
    'Initial Fees': 'init fees' 
} 

如果要引入其他變量,比如文件路徑(filepath),我們只需要找到包含“Filepath”的單元格,並把它的值賦給“filepath”:

row, _ = search_col(ws, 'C', 'Filepath') 
mappings['filepath'] = ws[f'D{row}].value 

集成
最後一步,也是最容易的一步——在 Python 腳本中使用列名。
我們使用上面得到的 mappings,將輸入列名轉成內部標籤。

data = pd.read_csv(mappings['Filepath']) 

在將輸入列名轉成內部標籤之前,我們必須翻轉鍵值對,即把鍵 - 值轉成值 - 鍵。

# 翻轉 
inv_mappings = {mappings[key]: key for key in mappings} 

對於這個簡單的例子,或許在構建 mappings 時就進行翻轉會更方便些。對於複雜一點的工具,我發現使用內部到外部的映射格式會更好。但不管怎樣,這一切取決於你自己。
最後,將輸入列名轉成內部標籤:

data.rename(inv_mappings, axis=1, inplace=True) 

我們可以做得更靈活一些。爲了處理不必要的空格或大小寫拼寫錯誤,我們重寫了一小部分代碼:

data = pd.read_csv(mappings['Filepath']) 
# 轉成小寫,剔除不必要的空格 
data.rename({col: col.strip().lower() for col in data.columns}, 
            axis=1, inplace=True) 
# inv_mappings 也是一樣 
# 內部標籤使用蛇形命名方式 (不是必需的) 
inv_mappings = { 
    mappings[key].strip().lower(): 
        key.strip().lower().replace(' ', '_') 
    for key in mappings 
} 
# 現在安全了 
data.rename(inv_mappings, axis=1, inplace=True) 

另外,我們在 Excel 中顯示內部標籤時通常會使用首字母大寫和正常空格,而在內部我個人還是選擇蛇形命名格式。

"Loan ID" -> "loan_id" 
"Initial Rate" -> "initial_rate" 

結論

我曾見過無數家重度使用 Excel 的公司,這麼做可以節省數百個小時用於檢查單元格、輸入值或等待 Excel 模型處理數據的時間。
儘管自動化和機器學習時代正在迅速地將 Excel 的很多領域自動化,但 Excel 不會很快就消失掉。
目前,世界上發展最快的編程語言(Python)和世界上使用最爲廣泛的軟件(Excel)之間的緊密集成可以給很多行業帶來巨大收益。

【雲棲號在線課堂】每天都有產品技術專家分享!
課程地址:https://yqh.aliyun.com/zhibo

立即加入社羣,與專家面對面,及時瞭解課程最新動態!
【雲棲號在線課堂 社羣】https://c.tb.cn/F3.Z8gvnK

原文發佈時間:2020-08-04
本文作者:James Briggs
本文來自:“InfoQ”,瞭解相關信息可以關注“InfoQ

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章