中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么用Python解決Excel問題

發布時間:2023-04-10 17:51:30 來源:億速云 閱讀:224 作者:iii 欄目:開發技術

本篇內容介紹了“怎么用Python解決Excel問題”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

「問題說明」

這次要處理的excel有兩個sheet,要根據其中一個sheet的數據來計算另外一個sheet的值。造成問題的點在于,要計算值的sheet里不僅僅有數值,還有公式。我們來看一下:

怎么用Python解決Excel問題

如上圖所示,這個excel一共有兩個sheet:CP和DS,我們要按照一定的業務規則,根據CP中的數據計算DS對應單元格的數據。圖中藍色方框框出來的是帶公式的,而其他區域是數值。

我們來看看,如果我們按照之前說的處理邏輯,把excel一次性批量讀取到dataframe處理,然后再一次性批量寫回去有啥問題。這部分代碼如下:

import pandas as pd
import xlwings as xw
 
#要處理的文件路徑
fpath = "data/DS_format.xlsm"
 
#把CP和DS兩個sheet的數據分別讀入pandas的dataframe
cp_df = pd.read_excel(fpath,sheet_name="CP",header=[0])
ds_df = pd.read_excel(fpath,sheet_name="DS",header=[0,1])
 
#計算過程省略......
 
#保存結果到excel       
app = xw.App(visible=False,add_book=False)
ds_format_workbook = app.books.open(fpath)
ds_worksheet = ds_format_workbook.sheets["DS"]
ds_worksheet.range("A1").expand().options(index=False).value = ds_df 
ds_format_workbook.save()
ds_format_workbook.close()
app.quit()

如上代碼存在的問題在于,pd.read_excel()方法從excel里讀取數據到dataframe的時候,對于有公式的單元格,會直接讀取公式計算的結果(如果沒有結果則返回Nan),而我們寫入excel的時候是直接把dataframe一次性批量寫回的,這樣之前帶公式的單元格,被寫回的就是計算出來的值或Nan,而丟掉了公式。

好了,問題出現了,我們該如何解決呢?這里會想到兩個思路:

  • dataframe寫回excel的時候,不要一次性批量寫回,而是通過行和列的迭代,只寫回計算的數據,有公式的單元格不動;

  • 讀取excel的時候,有沒有辦法做到對于有公式的單元格,讀取公式,而不是讀取公式計算的結果;

我確實按照上面兩個思路分別嘗試了一下,我們一起來看一下。

「方案1」

如下代碼嘗試遍歷dataframe然后按單元格寫入對應的值,有公式的單元格不動

#根據ds_df來寫excel,只寫該寫的單元格
for row_idx,row in ds_df.iterrows():
    total_capabity_val = row[('Total','Capabity')].strip()
    total_capabity1_val = row[('Total','Capabity.1')].strip()
    #Total和1Gb  Eqv.所在的行不寫
    if total_capabity_val!= 'Total' and total_capabity_val != '1Gb  Eqv.':
        #給Delta和LOI賦值
        if total_capabity1_val == 'LOI' or total_capabity1_val == 'Delta':
            ds_worksheet.range((row_idx + 3 ,3)).value = row[('Current week','BOH')]
            print(f"ds_sheet的第{row_idx + 3}行第3列被設置為{row[('Current week','BOH')]}") 
        #給Demand和Supply賦值
        if total_capabity1_val == 'Demand' or total_capabity1_val == 'Supply':
            cp_datetime_columns = cp_df.columns[53:]
            for col_idx in range(4,len(ds_df.columns)):
                ds_datetime = ds_df.columns.get_level_values(1)[col_idx]
                ds_month = ds_df.columns.get_level_values(0)[col_idx]
                if type(ds_datetime) == str and ds_datetime != 'TTL' and ds_datetime != 'Total' and (ds_datetime in cp_datetime_columns):
                    ds_worksheet.range((row_idx + 3,col_idx + 1)).value = row[(f'{ds_month}',f'{ds_datetime}')]
                    print(f"ds_sheet的第{row_idx + 3}行第{col_idx + 1}列被設置為{row[(f'{ds_month}',f'{ds_datetime}')]}") 
                elif type(ds_datetime) == datetime.datetime and (ds_datetime in cp_datetime_columns):
                    ds_worksheet.range((row_idx + 3,col_idx + 1)).value = row[(f'{ds_month}',ds_datetime)]     
                    print(f"ds_sheet的第{row_idx + 3}行第{col_idx + 1}列被設置為{row[(f'{ds_month}',ds_datetime)]}")

如上的代碼確實解決了問題,也即有公式的單元格的公式被保留了。但是,根據我們文章開頭提到的Python處理excel的忠告,這個代碼是有嚴重性能問題的,因為它通過api頻繁操作excel的單元格,導致寫入非常慢,在我的老邁Mac本上一共跑了40分鐘,簡直不可接受,故該方案只能放棄。

「方案2」

這個方案是希望做到讀取excel有公式值的單元格的時候,能保留公式值。這只能從各個Python的excel庫的API來尋找有無對應的方法了。Pandas的read_excel()方法我仔細看了一下沒有對應的參數可以支持。Openpyxl我倒是找到了一個API可以支持,如下:

import openpyxl
ds_format_workbook = openpyxl.load_workbook(fpath,data_only=False)
ds_wooksheet = ds_format_workbook['DS']
ds_df =  pd.DataFrame(ds_wooksheet.values)

關鍵是這里的data_only參數,為True則返回數據,為False的情況下可以保留公式值

本以為找到了對應解決方案正一頓竊喜,但當我看到通過openpyxl讀取到dataframe中的數據結構的時候,才被破了一盆冷水。因為我的excel表的表頭是比較復雜的兩級的表頭,表頭中還存在合并和拆分單元格的情況,這樣的表頭被openpyxl讀取到dataframe后,沒有按照pandas的多級索引進行處理,而是簡單的被處理成數字索引0123...

但我對dataframe的計算會依賴多級索引,因此openpyxl的這種處理方式導致我后面的計算無法處理。

openpyxl不行,再看看xlwings呢?通過對xlwings API文檔的一通尋找,還真給我找到了,如下所示:

怎么用Python解決Excel問題

Range類提供了一個Property叫formula,可以獲取和設置formula。

看到這個我簡直如獲至寶,趕緊代碼操練起來。也許出于慣性,又或許是被之前按行列單元格操作excel的效率搞怕了,我直接先想到的方案還是一次性批量搞定,也即一次性讀取excel所有的公式,然后再一次性寫回去,所以我一開始的代碼是這樣的:

#使用xlwings來讀取formula
app = xw.App(visible=False,add_book=False)
ds_format_workbook = app.books.open(fpath)
ds_worksheet = ds_format_workbook.sheets["DS"]
#先把所有公式一次性讀取并保存下來
formulas = ds_worksheet.used_range.formula
 
#中間計算過程省略...
 
#一次性把所有公式寫回去
ds_worksheet.used_range.formula = formulas

可是我想錯了,ds_worksheet.used_range.formula讓我誤解只會返回excel中的有公式的單元格的公式,但其實它返回的是所有的單元格,只是對有公式的單元格保留了公式。所以,當我重新寫回公式的時候,會覆蓋掉我通過dataframe計算完并寫入excel的其他的值。

既然這樣的話,那我只能對有公式的單元格分別處理而不是一次性處理了,所以代碼得這樣寫:

#使用xlwings來讀取formula
app = xw.App(visible=False,add_book=False)
ds_format_workbook = app.books.open(fpath)
ds_worksheet = ds_format_workbook.sheets["DS"]
 
#保留excel中的formula
#找到DS中Total所在的行,Total之后的行都是formula
row = ds_df.loc[ds_df[('Total','Capabity')]=='Total ']
total_row_index = row.index.values[0]
#獲取對應excel的行號(dataframe把兩層表頭當做索引,從數據行開始計數,而且從0開始計數。excel從表頭就開始計數,而且從1開始計數)
excel_total_row_idx = int(total_row_index+2)
#獲取excel最后一行的索引
excel_last_row_idx = ds_worksheet.used_range.rows.count
#保留按日期計算的各列的formula
I_col_formula = ds_worksheet.range(f'I3:I{excel_total_row_idx}').formula
N_col_formula = ds_worksheet.range(f'N3:N{excel_total_row_idx}').formula
T_col_formula = ds_worksheet.range(f'T3:T{excel_total_row_idx}').formula
U_col_formula = ds_worksheet.range(f'U3:U{excel_total_row_idx}').formula
Z_col_formula = ds_worksheet.range(f'Z3:Z{excel_total_row_idx}').formula
AE_col_formula = ds_worksheet.range(f'AE3:AE{excel_total_row_idx}').formula
AK_col_formula = ds_worksheet.range(f'AK3:AK{excel_total_row_idx}').formula
AL_col_formula = ds_worksheet.range(f'AL3:AL{excel_total_row_idx}').formula
#保留Total行開始一直到末尾所有行的formula
total_to_last_formula = ds_worksheet.range(f'A{excel_total_row_idx+1}:AL{excel_last_row_idx}').formula
 
#中間計算過程省略...
 
#保存結果到excel                 
#直接把ds_df完整賦值給excel,會導致excel原有的公式被值覆蓋
ds_worksheet.range("A1").expand().options(index=False).value = ds_df 
#用之前保留的formulas,重置公式
ds_worksheet.range(f'I3:I{excel_total_row_idx}').formula = I_col_formula
ds_worksheet.range(f'N3:N{excel_total_row_idx}').formula = N_col_formula
ds_worksheet.range(f'T3:T{excel_total_row_idx}').formula = T_col_formula
ds_worksheet.range(f'U3:U{excel_total_row_idx}').formula = U_col_formula
ds_worksheet.range(f'Z3:Z{excel_total_row_idx}').formula = Z_col_formula
ds_worksheet.range(f'AE3:AE{excel_total_row_idx}').formula = AE_col_formula
ds_worksheet.range(f'AK3:AK{excel_total_row_idx}').formula = AK_col_formula
ds_worksheet.range(f'AL3:AL{excel_total_row_idx}').formula = AL_col_formula
ds_worksheet.range(f'A{excel_total_row_idx+1}:AL{excel_last_row_idx}').formula = total_to_last_formula
 
ds_format_workbook.save()
ds_format_workbook.close()
app.quit()

經測試,如上代碼完美地解決我的需求,而且性能上也完全沒問題。

“怎么用Python解決Excel問題”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

吴桥县| 建阳市| 广元市| 宁德市| 抚顺县| 蒙城县| 北辰区| 沙田区| 镇赉县| 三台县| 吉林市| 青龙| 海门市| 瓮安县| 舒城县| 茂名市| 达尔| 图木舒克市| 新田县| 大英县| 宣汉县| 霍林郭勒市| 桦川县| 承德县| 郯城县| 兖州市| 津市市| 肇庆市| 靖江市| 鹿泉市| 夹江县| 秦皇岛市| 巫溪县| 辽阳县| 镇雄县| 繁昌县| 广西| 连城县| 资源县| 油尖旺区| 姚安县|