web-dev-qa-db-ja.com

Pythonでopenpyxlを使用してExcelスプレッドシートに行を挿入します

私は、openpyxlを使用してスプレッドシートに行を挿入するための最良の方法を探しています。

実際には、ヘッダー行とそれに続く(最大で)数千行のデータを持つスプレッドシート(​​Excel 2007)があります。行を実際のデータの最初の行として、ヘッダーの後に挿入しようとしています。私の理解では、追加機能はコンテンツをファイルの末尾に追加するのに適しているということです。

Openpyxlとxlrd(およびxlwt)の両方のドキュメントを読んで、コンテンツを手動でループして新しいシートに挿入する(必要な行を挿入した後)以外に、これを行う明確な方法は見つかりません。

私のこれまでのPythonの経験が限られていることを考えると、これが本当に最適な選択肢であるか(最もPythonic!)を理解しようとしています。具体的には、openpyxlで行を読み書きできますか、またはセルにアクセスする必要がありますか?さらに、同じファイル(名前)を(上書き)上書きできますか?

13
Nick

希望する結果を達成するために現在使用しているコードでこれに答えます。位置1に行を手動で挿入していますが、特定のニーズに合わせて簡単に調整できるはずです。これを簡単に調整して複数の行を挿入し、関連する位置から残りのデータを入力することもできます。

また、ダウンストリームの依存関係により、手動で「Sheet1」からデータを指定し、元のワークシートの名前を「Sheet1.5」に変更しながら、データがワークブックの先頭に挿入される新しいシートにコピーされることに注意してください。 。

編集:また、ここでデフォルトのコピー操作がすべてのフォーマットを削除する問題を修正するためにformat_codeに変更を追加しました(後で):new_cell.style.number_format.format_code = 'mm/dd/yyyy'。これが設定可能であるというドキュメントを見つけることができませんでした。これは、試行錯誤の結果でした。

最後に、この例がオリジナルを上書きすることを忘れないでください。これを回避するために、必要に応じて保存パスを変更できます。

    import openpyxl

    wb = openpyxl.load_workbook(file)
    old_sheet = wb.get_sheet_by_name('Sheet1')
    old_sheet.title = 'Sheet1.5'
    max_row = old_sheet.get_highest_row()
    max_col = old_sheet.get_highest_column()
    wb.create_sheet(0, 'Sheet1')

    new_sheet = wb.get_sheet_by_name('Sheet1')

    # Do the header.
    for col_num in range(0, max_col):
        new_sheet.cell(row=0, column=col_num).value = old_sheet.cell(row=0, column=col_num).value

    # The row to be inserted. We're manually populating each cell.
    new_sheet.cell(row=1, column=0).value = 'DUMMY'
    new_sheet.cell(row=1, column=1).value = 'DUMMY'

    # Now do the rest of it. Note the row offset.
    for row_num in range(1, max_row):
        for col_num in range (0, max_col):
            new_sheet.cell(row = (row_num + 1), column = col_num).value = old_sheet.cell(row = row_num, column = col_num).value

    wb.save(file)
9
Nick

==ここでのフィードバックに基づいて、完全に機能するバージョンに更新しました:groups.google.com/forum/#!topic/openpyxl-users/wHGecdQg3Iw。 ==

他の人が指摘したように、openpyxlはこの機能を提供しませんが、行の挿入を実装するためにWorksheetクラスを次のように拡張しました。これが他の人に役立つことを願っています。

def insert_rows(self, row_idx, cnt, above=False, copy_style=True, fill_formulae=True):
    """Inserts new (empty) rows into worksheet at specified row index.

    :param row_idx: Row index specifying where to insert new rows.
    :param cnt: Number of rows to insert.
    :param above: Set True to insert rows above specified row index.
    :param copy_style: Set True if new rows should copy style of immediately above row.
    :param fill_formulae: Set True if new rows should take on formula from immediately above row, filled with references new to rows.

    Usage:

    * insert_rows(2, 10, above=True, copy_style=False)

    """
    CELL_RE  = re.compile("(?P<col>\$?[A-Z]+)(?P<row>\$?\d+)")

    row_idx = row_idx - 1 if above else row_idx

    def replace(m):
        row = m.group('row')
        prefix = "$" if row.find("$") != -1 else ""
        row = int(row.replace("$",""))
        row += cnt if row > row_idx else 0
        return m.group('col') + prefix + str(row)

    # First, we shift all cells down cnt rows...
    old_cells = set()
    old_fas   = set()
    new_cells = dict()
    new_fas   = dict()
    for c in self._cells.values():

        old_coor = c.coordinate

        # Shift all references to anything below row_idx
        if c.data_type == Cell.TYPE_FORMULA:
            c.value = CELL_RE.sub(
                replace,
                c.value
            )
            # Here, we need to properly update the formula references to reflect new row indices
            if old_coor in self.formula_attributes and 'ref' in self.formula_attributes[old_coor]:
                self.formula_attributes[old_coor]['ref'] = CELL_RE.sub(
                    replace,
                    self.formula_attributes[old_coor]['ref']
                )

        # Do the magic to set up our actual shift    
        if c.row > row_idx:
            old_coor = c.coordinate
            old_cells.add((c.row,c.col_idx))
            c.row += cnt
            new_cells[(c.row,c.col_idx)] = c
            if old_coor in self.formula_attributes:
                old_fas.add(old_coor)
                fa = self.formula_attributes[old_coor].copy()
                new_fas[c.coordinate] = fa

    for coor in old_cells:
        del self._cells[coor]
    self._cells.update(new_cells)

    for fa in old_fas:
        del self.formula_attributes[fa]
    self.formula_attributes.update(new_fas)

    # Next, we need to shift all the Row Dimensions below our new rows down by cnt...
    for row in range(len(self.row_dimensions)-1+cnt,row_idx+cnt,-1):
        new_rd = copy.copy(self.row_dimensions[row-cnt])
        new_rd.index = row
        self.row_dimensions[row] = new_rd
        del self.row_dimensions[row-cnt]

    # Now, create our new rows, with all the pretty cells
    row_idx += 1
    for row in range(row_idx,row_idx+cnt):
        # Create a Row Dimension for our new row
        new_rd = copy.copy(self.row_dimensions[row-1])
        new_rd.index = row
        self.row_dimensions[row] = new_rd
        for col in range(1,self.max_column):
            col = get_column_letter(col)
            cell = self.cell('%s%d'%(col,row))
            cell.value = None
            source = self.cell('%s%d'%(col,row-1))
            if copy_style:
                cell.number_format = source.number_format
                cell.font      = source.font.copy()
                cell.alignment = source.alignment.copy()
                cell.border    = source.border.copy()
                cell.fill      = source.fill.copy()
            if fill_formulae and source.data_type == Cell.TYPE_FORMULA:
                s_coor = source.coordinate
                if s_coor in self.formula_attributes and 'ref' not in self.formula_attributes[s_coor]:
                    fa = self.formula_attributes[s_coor].copy()
                    self.formula_attributes[cell.coordinate] = fa
                # print("Copying formula from cell %s%d to %s%d"%(col,row-1,col,row))
                cell.value = re.sub(
                    "(\$?[A-Z]{1,3}\$?)%d"%(row - 1),
                    lambda m: m.group(1) + str(row),
                    source.value
                )   
                cell.data_type = Cell.TYPE_FORMULA

    # Check for Merged Cell Ranges that need to be expanded to contain new cells
    for cr_idx, cr in enumerate(self.merged_cell_ranges):
        self.merged_cell_ranges[cr_idx] = CELL_RE.sub(
            replace,
            cr
        )

Worksheet.insert_rows = insert_rows
17
Dallas

openpyxlの最新リリースv2.5 +に適用可能な回答を追加します。

insert_rows()insert_cols()があります。

insert_rows(idx, amount=1)

Row == idxの前に行を挿入します

6
aneroid

Openpyxl Worksheetsは、行または列レベルの操作を行う際の機能が制限されています。行/列に関連するワークシートのプロパティは、プロパティ_row_dimensions_および_column_dimensions_のみです。これらは、各行および列の「RowDimensions」オブジェクトと「ColumnDimensions」オブジェクトをそれぞれ格納します。これらの辞書は、get_highest_row()get_highest_column()などの関数でも使用されます。

他のすべてはセルレベルで動作し、Cellオブジェクトは辞書__cells_(および辞書__styles_で追跡されるスタイル)で追跡されます。行または列レベルで何かをしているように見えるほとんどの関数は、実際にはセルの範囲で動作しています(前述のappend()など)。

最も簡単なことは、提案したことです:新しいシートを作成し、ヘッダー行を追加し、新しいデータ行を追加し、古いデータ行を追加し、古いシートを削除してから、新しいシートの名前を古いものに変更します。このメソッドで発生する可能性のある問題は、行/列のディメンション属性とセルスタイルが失われることです(特にコピーしない限り)。

または、行または列を挿入する独自の関数を作成できます。

列を削除するために必要なveryシンプルなワークシートが多数ありました。あなたが明示的な例を求めたので、私はすぐにこれを行うために一緒に投げた機能を提供します:

_from openpyxl.cell import get_column_letter

def ws_delete_column(sheet, del_column):

    for row_num in range(1, sheet.get_highest_row()+1):
        for col_num in range(del_column, sheet.get_highest_column()+1):

            coordinate = '%s%s' % (get_column_letter(col_num),
                                   row_num)
            adj_coordinate = '%s%s' % (get_column_letter(col_num + 1),
                                       row_num)

            # Handle Styles.
            # This is important to do if you have any differing
            # 'types' of data being stored, as you may otherwise get
            # an output Worksheet that's got improperly formatted cells.
            # Or worse, an error gets thrown because you tried to copy
            # a string value into a cell that's styled as a date.

            if adj_coordinate in sheet._styles:
                sheet._styles[coordinate] = sheet._styles[adj_coordinate]
                sheet._styles.pop(adj_coordinate, None)
            else:
                sheet._styles.pop(coordinate, None)

            if adj_coordinate in sheet._cells:
                sheet._cells[coordinate] = sheet._cells[adj_coordinate]
                sheet._cells[coordinate].column = get_column_letter(col_num)
                sheet._cells[coordinate].row = row_num
                sheet._cells[coordinate].coordinate = coordinate

                sheet._cells.pop(adj_coordinate, None)
            else:
                sheet._cells.pop(coordinate, None)

        # sheet.garbage_collect()
_

作業中のワークシートと削除したい列番号を渡すと、消えます。私はそれがあなたが望んでいたものと正確に一致しないことを知っていますが、この情報が助けになることを願っています!

編集:誰かがこの別の票を投じたことに気づき、更新する必要があると考えました。 Openpyxlの座標系は、ここ数年の間に、__cell_のアイテムにcoordinate属性を導入して、いくつかの変更を経験しました。これも編集する必要があります。そうしないと、行は削除されずに空白のままになり、Excelはファイルの問題に関するエラーをスローします。これはOpenpyxl 2.2.3で機能します(それ以降のバージョンではテストされていません)

5
Rejected

Openpyxl 1.5以降、.insert_rows(idx、row_qty)を使用できるようになりました

from openpyxl import load_workbook
wb = load_workbook('Excel_template.xlsx')
ws = wb.active
ws.insert_rows(14, 10)

Excelで手動で行った場合のように、idx行のフォーマットを選択しません。正しいフォーマット、つまりセルの色を後で適用します。

2
PrestonDocks

ダラスのソリューションを取り、結合されたセルのサポートを追加しました。

    def insert_rows(self, row_idx, cnt, above=False, copy_style=True, fill_formulae=True):
        skip_list = []
        try:
            idx = row_idx - 1 if above else row_idx
            for (new, old) in Zip(range(self.max_row+cnt,idx+cnt,-1),range(self.max_row,idx,-1)):
                for c_idx in range(1,self.max_column):
                  col = self.cell(row=1, column=c_idx).column #get_column_letter(c_idx)
                  print("Copying %s%d to %s%d."%(col,old,col,new))
                  source = self["%s%d"%(col,old)]
                  target = self["%s%d"%(col,new)]
                  if source.coordinate in skip_list:
                      continue

                  if source.coordinate in self.merged_cells:
                      # This is a merged cell
                      for _range in self.merged_cell_ranges:
                          merged_cells_list = [x for x in cells_from_range(_range)][0]
                          if source.coordinate in merged_cells_list:
                              skip_list = merged_cells_list
                              self.unmerge_cells(_range)
                              new_range = re.sub(str(old),str(new),_range)
                              self.merge_cells(new_range)
                              break

                  if source.data_type == Cell.TYPE_FORMULA:
                    target.value = re.sub(
                      "(\$?[A-Z]{1,3})%d"%(old),
                      lambda m: m.group(1) + str(new),
                      source.value
                    )
                  else:
                    target.value = source.value
                  target.number_format = source.number_format
                  target.font   = source.font.copy()
                  target.alignment = source.alignment.copy()
                  target.border = source.border.copy()
                  target.fill   = source.fill.copy()
            idx = idx + 1
            for row in range(idx,idx+cnt):
                for c_idx in range(1,self.max_column):
                  col = self.cell(row=1, column=c_idx).column #get_column_letter(c_idx)
                  #print("Clearing value in cell %s%d"%(col,row))
                  cell = self["%s%d"%(col,row)]
                  cell.value = None
                  source = self["%s%d"%(col,row-1)]
                  if copy_style:
                    cell.number_format = source.number_format
                    cell.font      = source.font.copy()
                    cell.alignment = source.alignment.copy()
                    cell.border    = source.border.copy()
                    cell.fill      = source.fill.copy()
                  if fill_formulae and source.data_type == Cell.TYPE_FORMULA:
                    #print("Copying formula from cell %s%d to %s%d"%(col,row-1,col,row))
                    cell.value = re.sub(
                      "(\$?[A-Z]{1,3})%d"%(row - 1),
                      lambda m: m.group(1) + str(row),
                      source.value
                    )
1
Ran S

Pythonでopenpyxlを使用してExcelスプレッドシートに行を挿入するには

以下のコードが役立ちます:-

import openpyxl

file = "xyz.xlsx"
#loading XL sheet bassed on file name provided by user
book = openpyxl.load_workbook(file)
#opening sheet whose index no is 0
sheet = book.worksheets[0]

#insert_rows(idx, amount=1) Insert row or rows before row==idx, amount will be no of 
#rows you want to add and it's optional
sheet.insert_rows(13)

列を挿入するためにも、openpyxlには同様の機能があります。つまり、insert_cols(idx、amount = 1)

0
yugal sinha

これは私のために働いた:

    openpyxl.worksheet.worksheet.Worksheet.insert_rows(wbs,idx=row,amount=2)

Row == idxの前に2行挿入します

参照: http://openpyxl.readthedocs.io/en/stable/api/openpyxl.worksheet.worksheet.html

0
ack

ニックのソリューションを編集したこのバージョンでは、開始行、挿入する行数、ファイル名を取り、必要な数の空白行を挿入します。

#! python 3

import openpyxl, sys

my_start = int(sys.argv[1])
my_rows = int(sys.argv[2])
str_wb = str(sys.argv[3])

wb = openpyxl.load_workbook(str_wb)
old_sheet = wb.get_sheet_by_name('Sheet')
mcol = old_sheet.max_column
mrow = old_sheet.max_row
old_sheet.title = 'Sheet1.5'
wb.create_sheet(index=0, title='Sheet')

new_sheet = wb.get_sheet_by_name('Sheet')

for row_num in range(1, my_start):
    for col_num in range(1, mcol + 1):
        new_sheet.cell(row = row_num, column = col_num).value = old_sheet.cell(row = row_num, column = col_num).value

for row_num in range(my_start + my_rows, mrow + my_rows):
    for col_num in range(1, mcol + 1):
        new_sheet.cell(row = (row_num + my_rows), column = col_num).value = old_sheet.cell(row = row_num, column = col_num).value

wb.save(str_wb)
0
mut3