【问题标题】:How to write to an existing excel file without overwriting data (using pandas)?如何在不覆盖数据的情况下写入现有的 excel 文件(使用 pandas)?
【发布时间】:2013-12-11 17:23:23
【问题描述】:

我使用 pandas 以下列方式写入 excel 文件:

import pandas

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

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

writer.save()

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

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

【问题讨论】:

  • 你能举个例子或ExcelReader吗?我在文档中没有找到类似的内容。
  • 我认为 pandas 中没有 ExcelReader 这样的东西。我使用 read_excel 从 excel 中读取数据。我认为它不会将数据保存到 Excel。
  • @nrathaus 似乎没有ExcelReader
  • 请注意,对于问题的确切含义,答案存在一些混淆。一些答案假设“Main”尚不存在,并且 OP 只是将新工作表添加到现有的 excel 工作簿中。其他人假设“Main”已经存在,并且 OP 想要将新数据附加到“Main”的底部。

标签: python excel python-2.7 pandas


【解决方案1】:

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()

【讨论】:

  • 你能解释一下 writer.sheets 的用途吗?
  • ExcelWriter 出于某种原因使用此变量来访问工作表。如果您将其留空,它将不知道工作表 Main 已经存在并会创建一个新工作表。
  • 此解决方案运行良好。不过它有一个缺点。它破坏了电子表格中的公式和连接。任何想法如何改变这种行为?
  • 你到底被什么弄坏了..?您可能会将此作为一个单独的问题提出,并用openpyxl 标记它并提供足够的详细信息:您有什么样的公式,如何更新数据,如何阻止公式。现在我就是帮不上忙,我不知道的东西太多了。
  • 可以与 .xlsm 文件一起使用吗?
【解决方案2】:

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

Here 你可以找到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()

用以下版本测试:

  • 熊猫 1.2.3
  • Openpyxl 3.0.5

【讨论】:

  • 这个解决方案对我来说很完美,这里发布的其他解决方案不起作用。非常感谢!只有一条评论:当文件不存在时,我收到错误“NameError:未定义全局名称'FileNotFoundError'”
  • @cholo14,感谢您指出这一点!我已经在 Python 3.x 上对其进行了测试,所以我错过了那个错误。我已经在答案中修复了它......
  • 这对我有用,但有没有办法维护 xlsx 格式(来自原始 xlsx 文件)?
  • 有没有办法写入列而不是只写入行?就像我想自动更新工作表一样,但不追加新行,但感谢列!
  • 从 pandas 1.2.0 开始,代码会产生问题(直到 1.1.5 都可以正常工作),引发 BadZipFile 异常,因为在您实例化 pd.ExcelWriter 时它会创建大小为 0 字节的空文件并覆盖现有文件。必须指定mode='a'。见stackoverflow.com/a/66585065/4046632stackoverflow.com/q/66471466/4046632
【解决方案3】:

使用openpyxlversion 2.4.0pandasversion 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!

【讨论】:

  • 这对我不起作用。如果已经有一个“Main”工作表,它将仅使用新数据创建一个名为“Main1”的新工作表,而“Main”工作表内容保持不变。
  • @Qululu 我认为这个问题在两个不同的目标之间可能存在混淆。这允许您向现有工作簿添加其他工作表。 旨在将其他数据附加到现有工作表。如果存在工作表命名冲突,它会重命名工作表。这是一项功能,而不是错误。
  • 正如@Qululu 所说,这只会创建更多具有不同名称的工作表。来自 MaxU 的第一个解决方案有效,您将获得的输出将是第一张表中的 df,根据需要多次(即,标题也乘以多次。)一个简单的技术:每次迭代您将数据框附加到列表中。最后你只需要连接。如果它们遵循相同的结构,将起到魅力的作用。 list_my_dfs = [df1, df2, ...] # 数据帧列表 my_dfs_together = pd.concat(list_my_df ) # 将我的数据帧合并到单个 df 中
  • @SusanaSilvaSantos,看看 T.C Proctor 在你之前发表的评论。 OP 想要将不存在的工作表添加到现有工作簿中。这段代码就是这样做的。将数据附加到工作簿中的现有工作表不是范围的一部分。如果不需要,这就足够了。
【解决方案4】:

从 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) 

【讨论】:

  • 为我覆盖。
  • @keramat 我认为这个问题在两个不同的目标之间可能存在混淆。这允许您向现有工作簿添加其他工作表。它旨在将其他数据附加到现有工作表。
  • mode = 'a' 添加更多工作表,但如果我想覆盖现有工作表上的数据怎么办?
【解决方案5】:

我知道这是一个较旧的线程,但这是您在搜索时找到的第一个项目,如果您需要在已创建的工作簿中保留图表,则上述解决方案不起作用。在这种情况下,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()

【讨论】:

  • 如果文件不存在,有没有办法先创建文件?
  • 是的,您浏览过文档吗? docs.xlwings.org/en/stable/api.html
  • wb = xw.Book(filename) 在他们的网站上说它会创建一本书。但它没有
  • wb = xw.Book() 创建一本新的空书,当您将路径传递给它时,您将尝试加载现有的书。
  • 注意:xlwings 与正在运行的 Excel 实例交互,因此不能在 Linux 上运行。
【解决方案6】:

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

【讨论】:

  • 这是this earlier answer的副本
  • 只是对未来的提醒,这不适用于 XslxWriter 选项。
  • 默认情况下它也不适用于engine=openpyxl,因为它只会添加一个名为the only worksheet1的新工作表
【解决方案7】:

老问题,但我猜有些人仍在搜索这个 - 所以...

我觉得这种方法很好,因为所有工作表都加载到工作表名称和数据框对的字典中,由 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)

【讨论】:

  • 这种方法有效,但是我的合并单元格、单元格颜色和单元格宽度没有保留。
  • 是的,使用这种方法会丢失格式类型,因为每个工作表都转换为 pandas 数据框(没有任何 excel 格式),然后在新的 excel 工作簿中从数据框转换为工作表(与原始文件同名)。似乎即将推出一种使用 openpyxl 的新“附加”方法,它可能会保留原始文件工作表格式? github.com/pandas-dev/pandas/pull/21251
【解决方案8】:

@MaxU 的解决方案不适用于更新版本的 python 和相关包。它引发了错误: “zipfile.BadZipFile:文件不是 zip 文件”

我生成了一个新版本的函数,它可以与更新版本的 python 和相关包一起正常工作并用 python 测试:3.9 | openpyxl:3.0.6 |熊猫:1.2.3

此外,我还为辅助函数添加了更多功能:

  1. 现在它会根据单元格内容宽度调整所有列的大小,并且所有变量都将可见(参见“resizeColumns”)
  2. 您可以处理 NaN,如果您希望 NaN 显示为 NaN 或空单元格(参见“na_rep”)
  3. 添加“startcol”,你可以决定从特定列开始写入,否则将从 col = 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 个线程,每个线程以 1s 的间隔写入一个数据帧行。
【解决方案9】:
def append_sheet_to_master(self, master_file_path, current_file_path, sheet_name):
    try:
        master_book = load_workbook(master_file_path)
        master_writer = pandas.ExcelWriter(master_file_path, engine='openpyxl')
        master_writer.book = master_book
        master_writer.sheets = dict((ws.title, ws) for ws in master_book.worksheets)
        current_frames = pandas.ExcelFile(current_file_path).parse(pandas.ExcelFile(current_file_path).sheet_names[0],
                                                               header=None,
                                                               index_col=None)
        current_frames.to_excel(master_writer, sheet_name, index=None, header=False)

        master_writer.save()
    except Exception as e:
        raise e

这很好用,唯一的问题是主文件(我们添加新工作表的文件)的格式丢失了。

【讨论】:

    【解决方案10】:
    writer = pd.ExcelWriter('prueba1.xlsx'engine='openpyxl',keep_date_col=True)
    

    “keep_date_col”希望对你有所帮助

    【讨论】:

      【解决方案11】:
      book = load_workbook(xlsFilename)
      writer = pd.ExcelWriter(self.xlsFilename)
      writer.book = book
      writer.sheets = dict((ws.title, ws) for ws in book.worksheets)
      df.to_excel(writer, sheet_name=sheetName, index=False)
      writer.save()
      

      【讨论】:

      • 虽然这可能会回答作者的问题,但它缺少一些解释性文字和/或文档链接。如果没有围绕它们的一些短语,原始代码 sn-ps 并不是很有帮助。您可能还会发现how to write a good answer 非常有帮助。请编辑您的答案。
      【解决方案12】:

      方法:

      • 如果不存在可以创建文件
      • 根据工作表名称附加到现有 excel
      import pandas as pd
      from openpyxl import load_workbook
      
      def write_to_excel(df, file):
          try:
              book = load_workbook(file)
              writer = pd.ExcelWriter(file, engine='openpyxl') 
              writer.book = book
              writer.sheets = dict((ws.title, ws) for ws in book.worksheets)
              df.to_excel(writer, **kwds)
              writer.save()
          except FileNotFoundError as e:
              df.to_excel(file, **kwds)
      

      用法:

      df_a = pd.DataFrame(range(10), columns=["a"])
      df_b = pd.DataFrame(range(10, 20), columns=["b"])
      write_to_excel(df_a, "test.xlsx", sheet_name="Sheet a", columns=['a'], index=False)
      write_to_excel(df_b, "test.xlsx", sheet_name="Sheet b", columns=['b'])
      

      【讨论】:

        【解决方案13】:

        @MaxU 的解决方案效果很好。我只有一个建议:

        如果指定了 truncate_sheet=True,则不应从现有工作表中保留“startrow”。我建议:

                if startrow is None and sheet_name in writer.book.sheetnames:
                    if not truncate_sheet: # truncate_sheet would use startrow if provided (or zero below)
                        startrow = writer.book[sheet_name].max_row
        
        

        【讨论】:

          【解决方案14】:

          我使用here描述的答案

          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()
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-05-24
            相关资源
            最近更新 更多