如何使用pandas将数据写入现有的Excel文件而不覆盖原有数据?

165

我使用pandas以以下方式将数据写入Excel文件:

import pandas

writer = pandas.ExcelWriter('Masterfile.xlsx') 

data_filtered.to_excel(writer, "Main", cols=['Diff1', 'Diff2'])

writer.save()

Masterfile.xlsx已经包含许多不同的选项卡。但是,它还没有包含“主要”。

Pandas正确地写入了“Main”工作表,但不幸的是它也删除了所有其他选项卡。


1
你能给一个ExcelReader的例子吗?我在文档中没有找到类似的东西。 - BP_
1
我认为在pandas中没有ExcelReader这样的东西。我使用read_excel从excel中读取数据。我不认为它会将数据保存到excel中。 - BP_
1
@nrathaus 看起来没有 ExcelReader - virtualxtc
请注意,有些答案对问题的要求存在一些混淆。有些答案假设“Main”尚不存在,而OP只是在现有的Excel工作簿中添加一个新工作表。其他人则认为“Main”已经存在,并且OP想要将新数据附加到“Main”的底部。 - T.C. Proctor
17个回答

189

Pandas文档说它使用openpyxl处理xlsx文件。快速浏览ExcelWriter中的代码可以得到一个线索,类似如下的代码可能有效:

import pandas
from openpyxl import load_workbook

book = load_workbook('Masterfile.xlsx')
writer = pandas.ExcelWriter('Masterfile.xlsx', engine='openpyxl') 
writer.book = book

## ExcelWriter for some reason uses writer.sheets to access the sheet.
## If you leave it empty it will not know that sheet Main is already there
## and will create a new sheet.

writer.sheets = dict((ws.title, ws) for ws in book.worksheets)

data_filtered.to_excel(writer, "Main", cols=['Diff1', 'Diff2'])

writer.save()

4
可以请问一下 writer.sheets 是什么意思吗? - BP_
6
由于某些原因,ExcelWriter使用此变量来访问工作表。如果留空,则它将不知道工作表"Main"已经存在,并将创建一个新的工作表。 - Ski
3
这个解决方案很好,但是有一个缺点。它会破坏电子表格内的公式和连接。有什么想法可以改变这种行为吗? - BP_
1
https://dev59.com/52Ij5IYBdhLWcg3wcEqI - BP_
2
它可以用于.xlsm文件吗? - dapaz
显示剩余8条评论

63

更新:从Pandas 1.3.0开始,以下函数将无法正常工作,因为DataFrame.to_excel()pd.ExcelWriter()函数已经更改 - 引入了一个新的if_sheet_exists参数,使得下面的函数无效。

在这里,您可以找到append_df_to_excel()更新版本,适用于Pandas 1.3.0+。


这是一个辅助函数:

import os
from openpyxl import load_workbook


def append_df_to_excel(filename, df, sheet_name='Sheet1', startrow=None,
                       truncate_sheet=False, 
                       **to_excel_kwargs):
    """
    Append a DataFrame [df] to existing Excel file [filename]
    into [sheet_name] Sheet.
    If [filename] doesn't exist, then this function will create it.

    @param filename: File path or existing ExcelWriter
                     (Example: '/path/to/file.xlsx')
    @param df: DataFrame to save to workbook
    @param sheet_name: Name of sheet which will contain DataFrame.
                       (default: 'Sheet1')
    @param startrow: upper left cell row to dump data frame.
                     Per default (startrow=None) calculate the last row
                     in the existing DF and write to the next row...
    @param truncate_sheet: truncate (remove and recreate) [sheet_name]
                           before writing DataFrame to Excel file
    @param to_excel_kwargs: arguments which will be passed to `DataFrame.to_excel()`
                            [can be a dictionary]
    @return: None

    Usage examples:

    >>> append_df_to_excel('d:/temp/test.xlsx', df)

    >>> append_df_to_excel('d:/temp/test.xlsx', df, header=None, index=False)

    >>> append_df_to_excel('d:/temp/test.xlsx', df, sheet_name='Sheet2',
                           index=False)

    >>> append_df_to_excel('d:/temp/test.xlsx', df, sheet_name='Sheet2', 
                           index=False, startrow=25)

    (c) [MaxU](https://stackoverflow.com/users/5741205/maxu?tab=profile)
    """
    # Excel file doesn't exist - saving and exiting
    if not os.path.isfile(filename):
        df.to_excel(
            filename,
            sheet_name=sheet_name, 
            startrow=startrow if startrow is not None else 0, 
            **to_excel_kwargs)
        return
    
    # ignore [engine] parameter if it was passed
    if 'engine' in to_excel_kwargs:
        to_excel_kwargs.pop('engine')

    writer = pd.ExcelWriter(filename, engine='openpyxl', mode='a')

    # try to open an existing workbook
    writer.book = load_workbook(filename)
    
    # get the last row in the existing Excel sheet
    # if it was not specified explicitly
    if startrow is None and sheet_name in writer.book.sheetnames:
        startrow = writer.book[sheet_name].max_row

    # truncate sheet
    if truncate_sheet and sheet_name in writer.book.sheetnames:
        # index of [sheet_name] sheet
        idx = writer.book.sheetnames.index(sheet_name)
        # remove [sheet_name]
        writer.book.remove(writer.book.worksheets[idx])
        # create an empty sheet [sheet_name] using old index
        writer.book.create_sheet(sheet_name, idx)
    
    # copy existing sheets
    writer.sheets = {ws.title:ws for ws in writer.book.worksheets}

    if startrow is None:
        startrow = 0

    # write out the new sheet
    df.to_excel(writer, sheet_name, startrow=startrow, **to_excel_kwargs)

    # save the workbook
    writer.save()

已测试以下版本:

  • Pandas 1.2.3
  • Openpyxl 3.0.5

(注意:本文不含解释,保留HTML标签)

2
这个解决方案对我非常有效,其他在这里发布的不起作用。非常感谢!只有一点评论:当文件不存在时,我会收到一个错误消息“NameError: global name 'FileNotFoundError' is not defined”。 - cholo14
3
@cholo14,感谢您指出这个问题!我已经在Python 3.x上对其进行了测试,因此错过了那个错误。我已经在回答中修复了它... - MaxU - stand with Ukraine
2
这对我有用,但是否有一种方法可以保留xlsx格式(来自原始xlsx文件)? - 2one
2
有没有办法写入列而不仅仅是行?就像我想自动更新一个表格,但不是添加新行,而是添加新列。谢谢! - doomdaam
3
截至pandas 1.2.0版本,此代码会出现问题(在1.1.5版本之前可以正常工作),会触发BadZipFile异常,因为在实例化pd.ExcelWriter时,它会创建一个大小为0字节的空文件并覆盖现有文件。需要指定mode='a'。请参见https://stackoverflow.com/a/66585065/4046632和https://dev59.com/_r_qa4cB1Zd3GeqPOLzB。 - buran
显示剩余15条评论

27

使用openpyxl版本2.4.0pandas版本0.19.2,@ski提出的过程变得更加简单:

import pandas
from openpyxl import load_workbook

with pandas.ExcelWriter('Masterfile.xlsx', engine='openpyxl') as writer:
    writer.book = load_workbook('Masterfile.xlsx')
    data_filtered.to_excel(writer, "Main", cols=['Diff1', 'Diff2'])
#That's it!

14
对我来说这个不起作用。如果已经有一个名为“Main”的工作表,它将创建一个名为“Main1”的新工作表,只包含新数据,并保持“Main”工作表内容不变。 - Qululu
4
@Qululu 我认为这个问题可能存在两个不同目标之间的混淆。此功能允许您向现有工作簿添加额外的工作表。它不是用于将附加数据追加到现有工作表中。如果出现工作表命名冲突,它会重命名该工作表。这是一个特性,而非错误。 - T.C. Proctor
正如@Qululu所说,这只会创建更多的工作表,并带有不同的名称。 第一个解决方案来自MaxU,您将获得的输出将是第一张工作表中的df,您可以根据需要重复多次(也就是说,标题也会相应地乘以多少次)。一个简单的技巧:每次迭代时将数据框附加到列表中。 最后,您只需要连接即可。如果它们遵循相同的结构,那么它将像魅力一样运行。list_my_dfs = [df1, df2, ...] # 您的数据框列表 my_dfs_together = pd.concat(list_my_df ) # 将我的数据框连接成一个数据框 - Susana Isabel Santos
@SusanaSilvaSantos,请看一下T.C Proctor在你之前发表的评论。原帖想要将一个不存在的工作表添加到现有的工作簿中。这段代码可以实现这一点。将数据附加到工作簿中现有的工作表不在讨论范围内。如果不需要这样做,这段代码就足够了。 - mvbentes
ExcelWriter有一个if_sheet_exists参数,允许您指定“replace”。此参数仅可与模式'a'一起使用。 - user3376851

25

从pandas 0.24开始,您可以使用ExcelWritermode关键字参数来简化此过程:

import pandas as pd

with pd.ExcelWriter('the_file.xlsx', engine='openpyxl', mode='a') as writer: 
     data_filtered.to_excel(writer) 

6
给我覆盖现有的内容。 - keramat
16
@keramat,我认为这个问题存在两个不同的目标之间的混淆。这允许您向现有工作簿中添加附加工作表。它不是用于向现有工作表追加附加数据。 - T.C. Proctor
5
如果想要覆盖现有工作表上的数据,mode='a' 会添加更多的工作表,那么该怎么办呢? - Confounded
1
ExcelWriter有一个if_sheet_exists参数,允许您指定“replace”。 此参数仅可与模式'a'一起使用。 - user3376851

12

我知道这是一个较旧的帖子,但这是搜索时第一个找到的结果,如果您需要保留已创建的工作簿中的图表,则上述解决方案无法使用。在这种情况下,xlwings是更好的选择 - 它允许您向Excel工作簿写入数据并保留图表/图表数据。

简单示例:

import xlwings as xw
import pandas as pd

#create DF
months = ['2017-01','2017-02','2017-03','2017-04','2017-05','2017-06','2017-07','2017-08','2017-09','2017-10','2017-11','2017-12']
value1 = [x * 5+5 for x in range(len(months))]
df = pd.DataFrame(value1, index = months, columns = ['value1'])
df['value2'] = df['value1']+5
df['value3'] = df['value2']+5

#load workbook that has a chart in it
wb = xw.Book('C:\\data\\bookwithChart.xlsx')

ws = wb.sheets['chartData']

ws.range('A1').options(index=False).value = df

wb = xw.Book('C:\\data\\bookwithChart_updated.xlsx')

xw.apps[0].quit()

有没有办法在文件不存在的情况下创建文件? - Tinkinc
是的,您有查看文档吗?http://docs.xlwings.org/en/stable/api.html - flyingmeatball
wb = xw.Book(filename) 在他们的网站上说它创建了一个工作簿,但实际上并没有。 - Tinkinc
wb = xw.Book()创建一个新的空工作簿,当您传递一个路径时,您正在尝试加载一个现有的工作簿。 - flyingmeatball
我无法获取引用选项卡以进行更新(即主干和加载数据)。有什么想法吗? - Tinkinc
1
注意:xlwings与正在运行的Excel实例进行交互,因此无法在Linux上运行。 - virtualxtc

11

虽然这是一个老问题,但我猜还有一些人在搜索 - 所以...

我认为这种方法很好,因为所有的工作表都被加载到一个字典中,由pandas使用sheetname=None选项创建了一个包含工作表名称和数据帧对的字典。在将电子表格读入字典格式并从字典写回时,添加、删除或修改工作表非常简单。对于我来说,xlsxwriter在速度和格式方面比openpyxl更好。

注意:pandas的未来版本(0.21.0+)将把“sheetname”参数更改为“sheet_name”。

# read a single or multi-sheet excel file
# (returns dict of sheetname(s), dataframe(s))
ws_dict = pd.read_excel(excel_file_path,
                        sheetname=None)

# all worksheets are accessible as dataframes.

# easy to change a worksheet as a dataframe:
mod_df = ws_dict['existing_worksheet']

# do work on mod_df...then reassign
ws_dict['existing_worksheet'] = mod_df

# add a dataframe to the workbook as a new worksheet with
# ws name, df as dict key, value:
ws_dict['new_worksheet'] = some_other_dataframe

# when done, write dictionary back to excel...
# xlsxwriter honors datetime and date formats
# (only included as example)...
with pd.ExcelWriter(excel_file_path,
                    engine='xlsxwriter',
                    datetime_format='yyyy-mm-dd',
                    date_format='yyyy-mm-dd') as writer:

    for ws_name, df_sheet in ws_dict.items():
        df_sheet.to_excel(writer, sheet_name=ws_name)

对于2013年问题的示例:

ws_dict = pd.read_excel('Masterfile.xlsx',
                        sheetname=None)

ws_dict['Main'] = data_filtered[['Diff1', 'Diff2']]

with pd.ExcelWriter('Masterfile.xlsx',
                    engine='xlsxwriter') as writer:

    for ws_name, df_sheet in ws_dict.items():
        df_sheet.to_excel(writer, sheet_name=ws_name)

1
这在某种程度上起作用了,但我的合并单元格、单元格颜色和单元格宽度没有得到保留。 - virtualxtc
1
是的,使用这种方法将会丢失那种格式,因为每个工作表都会被转换为一个pandas数据帧(没有那个Excel格式),然后在新的Excel工作簿中从数据帧转换为工作表(该工作簿与原始文件同名)。看起来,即将推出一种新的“append”方法,使用openpyxl可能会保留原始文件工作表格式?https://github.com/pandas-dev/pandas/pull/21251 - b2002

10

pandas 0.24 中有更好的解决方案:

with pd.ExcelWriter(path, mode='a') as writer:
    s.to_excel(writer, sheet_name='another sheet', index=False)

之前:

输入图像描述

之后:

输入图像描述

因此现在请升级您的pandas:

pip install --upgrade pandas

1
这是此前的回答的副本。 - T.C. Proctor
3
提醒一下,这个选项不适用于 XslxWriter - metinsenturk
默认情况下,它不适用于 engine=openpyxl,因为它只会添加一个名为 the only worksheet1 的新工作表。 - Björn

3
@MaxU的解决方案在更新版本的python和相关包中不起作用。它会引发错误:"zipfile.BadZipFile:文件不是zip文件" 我生成了一个新版本的函数,可以与更新版本的python和相关包很好地配合使用,并测试了python:3.9 | openpyxl:3.0.6 | pandas:1.2.3
此外,我还向助手函数添加了更多功能:
  1. 现在它可以根据单元格内容宽度调整所有列的大小,并且所有变量都将可见(请参见“resizeColumns”)
  2. 如果你希望NaN显示为NaN或为空单元格,可以处理NaN(请参见“na_rep”)
  3. 新增了“startcol”,您可以决定从特定列开始写入,否则将从列= 0开始
以下是函数代码:
import pandas as pd

def append_df_to_excel(filename, df, sheet_name='Sheet1', startrow=None, startcol=None,
    truncate_sheet=False, resizeColumns=True, na_rep = 'NA', **to_excel_kwargs):
    """
    Append a DataFrame [df] to existing Excel file [filename]
    into [sheet_name] Sheet.
    If [filename] doesn't exist, then this function will create it.

    Parameters:
      filename : File path or existing ExcelWriter
                 (Example: '/path/to/file.xlsx')
      df : dataframe to save to workbook
      sheet_name : Name of sheet which will contain DataFrame.
                   (default: 'Sheet1')
      startrow : upper left cell row to dump data frame.
                 Per default (startrow=None) calculate the last row
                 in the existing DF and write to the next row...
      truncate_sheet : truncate (remove and recreate) [sheet_name]
                       before writing DataFrame to Excel file

      resizeColumns: default = True . It resize all columns based on cell content width
      to_excel_kwargs : arguments which will be passed to `DataFrame.to_excel()`
                        [can be dictionary]
      na_rep: default = 'NA'. If, instead of NaN, you want blank cells, just edit as follows: na_rep=''


    Returns: None

    *******************

    CONTRIBUTION:
    Current helper function generated by [Baggio]: https://stackoverflow.com/users/14302009/baggio?tab=profile
    Contributions to the current helper function: https://stackoverflow.com/users/4046632/buran?tab=profile
    Original helper function: (c) [MaxU](https://stackoverflow.com/users/5741205/maxu?tab=profile)


    Features of the new helper function:
    1) Now it works with python 3.9 and latest versions of pandas and openpxl
    ---> Fixed the error: "zipfile.BadZipFile: File is not a zip file".
    2) Now It resize all columns based on cell content width AND all variables will be visible (SEE "resizeColumns")
    3) You can handle NaN,  if you want that NaN are displayed as NaN or as empty cells (SEE "na_rep")
    4) Added "startcol", you can decide to start to write from specific column, oterwise will start from col = 0

    *******************



    """
    from openpyxl import load_workbook
    from string import ascii_uppercase
    from openpyxl.utils import get_column_letter
    from openpyxl import Workbook

    # ignore [engine] parameter if it was passed
    if 'engine' in to_excel_kwargs:
        to_excel_kwargs.pop('engine')

    try:
        f = open(filename)
        # Do something with the file
    except IOError:
        # print("File not accessible")
        wb = Workbook()
        ws = wb.active
        ws.title = sheet_name
        wb.save(filename)

    writer = pd.ExcelWriter(filename, engine='openpyxl', mode='a')


    # Python 2.x: define [FileNotFoundError] exception if it doesn't exist
    try:
        FileNotFoundError
    except NameError:
        FileNotFoundError = IOError


    try:
        # try to open an existing workbook
        writer.book = load_workbook(filename)

        # get the last row in the existing Excel sheet
        # if it was not specified explicitly
        if startrow is None and sheet_name in writer.book.sheetnames:
            startrow = writer.book[sheet_name].max_row

        # truncate sheet
        if truncate_sheet and sheet_name in writer.book.sheetnames:
            # index of [sheet_name] sheet
            idx = writer.book.sheetnames.index(sheet_name)
            # remove [sheet_name]
            writer.book.remove(writer.book.worksheets[idx])
            # create an empty sheet [sheet_name] using old index
            writer.book.create_sheet(sheet_name, idx)

        # copy existing sheets
        writer.sheets = {ws.title:ws for ws in writer.book.worksheets}
    except FileNotFoundError:
        # file does not exist yet, we will create it
        pass

    if startrow is None:
        # startrow = -1
        startrow = 0

    if startcol is None:
        startcol = 0

    # write out the new sheet
    df.to_excel(writer, sheet_name, startrow=startrow, startcol=startcol, na_rep=na_rep, **to_excel_kwargs)


    if resizeColumns:

        ws = writer.book[sheet_name]

        def auto_format_cell_width(ws):
            for letter in range(1,ws.max_column):
                maximum_value = 0
                for cell in ws[get_column_letter(letter)]:
                    val_to_check = len(str(cell.value))
                    if val_to_check > maximum_value:
                        maximum_value = val_to_check
                ws.column_dimensions[get_column_letter(letter)].width = maximum_value + 2

        auto_format_cell_width(ws)

    # save the workbook
    writer.save()

示例用法:

# Create a sample dataframe
df = pd.DataFrame({'numbers': [1, 2, 3],
                    'colors': ['red', 'white', 'blue'],
                    'colorsTwo': ['yellow', 'white', 'blue'],
                    'NaNcheck': [float('NaN'), 1, float('NaN')],
                    })

# EDIT YOUR PATH FOR THE EXPORT 
filename = r"C:\DataScience\df.xlsx"   

# RUN ONE BY ONE IN ROW THE FOLLOWING LINES, TO SEE THE DIFFERENT UPDATES TO THE EXCELFILE 
  
append_df_to_excel(filename, df, index=False, startrow=0) # Basic Export of df in default sheet (Sheet1)
append_df_to_excel(filename, df, sheet_name="Cool", index=False, startrow=0) # Append the sheet "Cool" where "df" is written
append_df_to_excel(filename, df, sheet_name="Cool", index=False) # Append another "df" to the sheet "Cool", just below the other "df" instance
append_df_to_excel(filename, df, sheet_name="Cool", index=False, startrow=0, startcol=5) # Append another "df" to the sheet "Cool" starting from col 5
append_df_to_excel(filename, df, index=False, truncate_sheet=True, startrow=10, na_rep = '') # Override (truncate) the "Sheet1", writing the df from row 10, and showing blank cells instead of NaN

这段代码帮了我很多忙。我会把它加入我的代码库。令人惊讶的是,即使打开了Excel文件,它也能正常工作。此外,这些函数是线程安全的,我尝试使用40个线程,每个线程以1秒的间隔将单个数据框行写入文件。 - n158
一个小改动可能需要:如果您使用的是 Pandas 1.4+ 版本,请在写入器分配中添加额外参数:if_sheet_exists='replace',例如:writer = pd.ExcelWriter(filename, engine='openpyxl', mode='a', if_sheet_exists='replace')。否则,您的解决方案完美运行。感谢您的努力和发布解决方案。 - DjangoRulz
由于某些原因,我仍然会得到以下错误:writer.book = load_workbook(filename) AttributeError: 无法设置属性 - Pavneet Singh

2

我使用了描述在这里的答案

from openpyxl import load_workbook
writer = pd.ExcelWriter(p_file_name, engine='openpyxl', mode='a')
writer.book = load_workbook(p_file_name)
writer.sheets = {ws.title:ws for ws in writer.book.worksheets}
df.to_excel(writer, 'Data', startrow=10, startcol=20)
writer.save()

1
writer = pd.ExcelWriter('prueba1.xlsx'engine='openpyxl',keep_date_col=True)

"keep_date_col"希望能够帮到您


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接