【问题标题】:Convert win32com.client Range to Pandas Dataframe?将 win32com.client Range 转换为 Pandas Dataframe?
【发布时间】:2022-01-04 23:03:36
【问题描述】:

我正在编写一些宏,它们调用 Python 代码来对 Excel 中的范围执行操作。在 Python 中使用 pandas 执行许多所需的操作要容易得多。因为我想在电子表格打开(并且可能尚未保存)时执行此操作,所以我使用win32com.client 读取一系列单元格以转换为 Pandas 数据框。但是,这非常慢,大概是因为我计算它的方式非常低效:

import datetime
import pytz
import pandas
import time
import win32com.client

def range_to_table(excelRange, tsy, tsx, height, width, add_cell_refs = True):
  ii = 0
  keys = []
  while ii < width:
    keys.append(str(excelRange[ii]))
    ii += 1
  colnumbers = {key:jj+tsx for jj, key in enumerate(keys)}
  keys.append('rownumber')
  mydict = {key:[] for key in keys}
  while ii < width*height:
    mydict[keys[ii%width]].append(excelRange[ii].value)
    ii += 1
  for yy in range(tsy + 1, tsy + 1 + height - 1): # add 1 to not include header
    mydict['rownumber'].append(yy)
  return (mydict, colnumbers)

ExcelApp = win32com.client.GetActiveObject('Excel.Application')
wb = ExcelApp.Workbooks('myworkbook.xlsm')
sheet_num = [sheet.Name for sheet in wb.Sheets].index("myworksheet name") + 1
ws = wb.Worksheets(sheet_num)

height = int(ws.Cells(1, 3)) # obtain table height from formula in excel spreadsheet
width = int(ws.Cells(1, 2)) # obtain table width from formula in excel spreadsheet

myrange = ws.Range(ws.Cells(2, 1), ws.Cells(2 + height - 1, 1 + width - 1))
df, colnumbers = range_to_table(myrange, 1, 1, height, width)
df = pandas.DataFrame.from_dict(df)

这可行,但我编写的 range_to_table 函数对于大型表格来说非常慢,因为它会逐个遍历每个单元格。

我怀疑可能有更好的方法将 Excel Range 对象转换为 Pandas 数据框。你知道更好的方法吗?

这是我的范围的简化示例:

代码中的heightwidth 变量只是取自表格上方的单元格:

这里有什么想法,还是我只需要保存工作簿并使用 Pandas 从保存的文件中读取表格?

【问题讨论】:

    标签: python excel pandas win32com


    【解决方案1】:

    操作分为两部分:定义电子表格范围,然后将数据导入 Python。这是我正在使用的测试数据:

    1.定义范围:Excel 有一个名为Dynamic Ranges 的功能。这允许您为范围可变的范围命名。

    我设置了一个名为“DynRange”的动态范围,您可以看到它使用 $C$1 和 $C$2 中的行数和列数来定义数组的大小。

    一旦你有了这个定义,范围就可以被 Python 中的 Name 使用,这样你就不必显式地访问行数和列数了。

    2。通过 win32.com 在 Python 中使用此范围: 在 Excel 中定义名称后,在 Python 中处理它会容易得多。

    import win32com.client as wc
    import pandas as pd
    
    #Create a dispatch interface
    xl = wc.gencache.EnsureDispatch('Excel.Application')
    
    filepath = 'SomeFilePath\\TestBook.xlsx'
    
    #Open the workbook
    wb = xl.Workbooks.Open(filepath)
    #Get the Worksheet by name
    ws = wb.Sheets('Sheet1')
    
    #Use the Value property to get all the data in the range
    listVals = ws.Range('DynRange').Value
    
    #Construct the dataframe, using first row as headers
    df = pd.DataFrame(listVals[1:],columns=listVals[0])
    #Optionally process the datetime value to avoid tz warnings
    df['Datetime'] = df['Datetime'].dt.tz_convert(None)
    
    print(df)
    
    wb.Close()
    

    输出:

                 Datetime Principal Source Amt Cost Basis
    0 2021-04-21 04:59:00      -5.0      1.001        5.0
    1 2021-04-25 15:16:00   -348.26      1.001       10.0
    2 2021-04-29 11:04:00       0.0      1.001        5.0
    3 2021-04-29 21:26:00       0.0      1.001        5.0
    4 2021-04-29 23:39:00       0.0      1.001        5.0
    5 2021-05-02 14:00:00   -2488.4      1.001        5.0
    

    正如 OP 所怀疑的那样,逐个单元格地迭代该范围的速度很慢。 COM 基础架构必须进行大量处理才能将数据从一个进程 (Excel) 传递到另一个进程 (Python)。这被称为“编组”。大部分时间都花在一方面打包变量,另一方面解包。一次性编组 Excel Range 的全部内容(作为 2D 数组)效率更高,并且 Excel 通过将 Range 的 Value 属性作为一个整体而不是单元格公开来实现这一点。

    【讨论】:

    • 这非常有效。速度提升是巨大的。
    • 谢谢,我删除了这个问题,因为我意识到在我必须转换另一个日期列后不久。感谢您的洞察力!
    【解决方案2】:

    您可以尝试为此使用多处理。例如,您可以让每个工作人员扫描不同的列,或者甚至在行上执行相同的操作。

    需要对您的代码进行少量更改:

    这应该将您的计算时间除以使用的工作人员数量。

    【讨论】:

    • 我认为在不同线程中使用相同的 Dispatch 接口会遇到问题。
    猜你喜欢
    • 2017-03-17
    • 2017-03-23
    • 2017-04-13
    • 2021-03-29
    • 2017-04-27
    • 2017-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多