0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】Excelと密に連携可能なpandas.DataFrameのサブクラス【Pandas】

Last updated at Posted at 2023-01-17

はじめに

昨日、Jupyter上にExcel風のスプレッドシートを作成する記事を拝見しました。
そして思いました。
シームレスに本家Excelとデータを受け渡しできれば、それが一番最強なのでは
と。

つまり「Excelでインタラクティブにデータを触りたい場合にはExcelにデータを渡して、Excelで編集したデータをPythonで使用したい場合にはExcelからデータを受け取る。」この当たり前の動作を、ファイル入出力などの面倒な操作を介さずに即座に行えれば非常に捗りそうです。

Desktop 2023.01.17 - 17.35.21.05.gif

どうしたか

Pythonで表形式のデータを扱う定番といえばPandasのDataFrameです。このクラスを継承して、Excelとの連携機能を追加します。

実装例

win32comは日時データの扱いに癖があるため少しごちゃついてますが、Excelを開いてデータを読み書きしているだけですので、やっていることは至ってシンプルです。
df.push()でExcelにデータを渡し、df.pull()でExcelからデータを受けとれます。それ以外は通常のpd.DataFrameと同様に扱うことができます。

import pandas as pd
from datetime import datetime
import win32com.client
import pywintypes
import numpy as np
from openpyxl.utils import get_column_letter


class DataFrame(pd.DataFrame):
    @property
    def _constructor(self):
        return DataFrame

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def push(self):
        self._connect()
        self.sheet.Cells.Clear()
        row, col = self.shape
        headers = [str(i) for i in self.columns]
        self.sheet.Range("A1", f"{get_column_letter(col)}1").NumberFormatLocal = "@"
        self.sheet.Range("A1", f"{get_column_letter(col)}1").Value = headers
        values = self._to_pydatetime(self.values)
        self.sheet.Range("A2", f"{get_column_letter(col)}{row+1}").Value = values

    def pull(self):
        self._connect()
        col, row = self._get_range()
        headers = self.sheet.Range("A1", f"{get_column_letter(col)}1").Value
        headers = headers[0] if isinstance(headers, tuple) else [headers]
        data = self._to_pydatetime(
            self.sheet.Range("A2", f"{get_column_letter(col)}{row+1}").Value
        )
        return DataFrame(data, columns=headers)

    def _get_range(self):
        return (
            self.sheet.Cells(1, self.sheet.Columns.Count).End(-4159).Column,
            self.sheet.Cells(self.sheet.Rows.Count, 1).End(-4162).Row - 1,
        )

    def _connect(self):
        try:
            self.app = win32com.client.GetActiveObject("Excel.Application")
            if not self.app.visible:
                self.app.visible = True
                self.workbook = self.app.Workbooks.Add()
            else:
                self.workbook = self.app.Workbooks[0]
            self.sheet = self.workbook.Worksheets("Sheet1")
        except Exception as e:
            self.app = win32com.client.Dispatch("Excel.Application")
            self.app.visible = True
            self.workbook = self.app.Workbooks.Add()
            self.sheet = self.workbook.Worksheets("Sheet1")

    def _to_pydatetime(self, x):
        def f(x):
            return (
                x.to_pydatetime()
                if isinstance(x, pd._libs.tslibs.timestamps.Timestamp)
                else datetime.fromisoformat(x.isoformat())
                if isinstance(x, pywintypes.TimeType)
                else x
            )

        return np.vectorize(f)(x)

おわりに

良いPythonライフを。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?