将 win32com.client 范围转换为 Pandas 数据框?

Convert win32com.client Range to Pandas Dataframe?

我正在编写一些调用 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 函数对于大型 tables 来说非常慢,因为它逐个迭代每个单元格。

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

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

代码中的 heightwidth 变量取自 table:

正上方的单元格

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

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

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

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

操作分为两部分:定义电子表格范围,然后将数据导入 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)。这被称为 'marshalling'。大部分时间都花在了一侧打包变量和另一侧拆包上。一次性编组 Excel 范围的全部内容(作为二维数组)效率更高,Excel 通过在 Value 属性 上公开允许这样做范围作为一个整体,而不是按单元格。