パンダスでエクセルを読み込み、読み込みの進捗を取得することができます。


前に書く
QQ群の中でたまたま群れの友達がこの問題を聞くことを見て、pandsが大きいファイルを読む時はどうやって進捗を取得できますか?私の第一反応はパンダスのリード以外です。excelなどの関数はコールバック関数のインターフェースを提供しています。そうでないとできないはずです。公式文書とネット上の書き込みを検索してみました。やはり既成の方案がないので、自分で作るしかないです。
準備工作
案を確定する
最初から実現案を確認しました。つまり、コールバック関数を追加します。ここでコペを売ってみます。コールバック関数とは何ですか?簡単に言えば、
使用しているモジュールの中で、外部メソッド/関数を呼び出します。コールバック関数です。今回の試みを例にとって、「表示進捗関数」を作成します。転送によってpd.read_に伝えられます。excel、このようにpdはexcelを読む時、読みながら「進捗関数を表示する」と呼びます。どうして直接pdの中で増加しないですか?excelファイルをpdで読み込むときはブロックされますので、内部メソッドは呼び出し時に進捗情報を破棄できません。
読み取り方式を理解する
まずパンdasはどうやってエクセルを読みますか?pycharmの中でcontrolを押してreadをクリックします。excel、もう一度コードを見て、キーの関数に従ってジャンプを続けますか?それとも呼び出されやすいパスですか?
在这里插入图片描述
最後にOpenpyxlReaderでエクセルを読み取る方法コードは以下の通りです。明らかなポイントはそのforサイクルにあります。get(u)を呼び出します。sheet_dataの場合、ターゲットsheetは一連の方法で獲得されました。その後、forサイクルでデータを逐行読み、dataに戻って最後にdata frameを生成します。

def get_sheet_data(self, sheet, convert_float: bool) -> List[List[Scalar]]:
        # GH 39001
        # Reading of excel file depends on dimension data being correct but
        # writers sometimes omit or get it wrong
        import openpyxl

        version = LooseVersion(get_version(openpyxl))

        # There is no good way of determining if a sheet is read-only
        # https://foss.heptapod.net/openpyxl/openpyxl/-/issues/1605
        is_readonly = hasattr(sheet, "reset_dimensions")

        if version >= "3.0.0" and is_readonly:
            sheet.reset_dimensions()

        data: List[List[Scalar]] = []
        last_row_with_data = -1
        for row_number, row in enumerate(sheet.rows):
            converted_row = [self._convert_cell(cell, convert_float) for cell in row]
            if not all(cell == "" for cell in converted_row):
                last_row_with_data = row_number
            data.append(converted_row)

        # Trim trailing empty rows
        data = data[: last_row_with_data + 1]

        if version >= "3.0.0" and is_readonly and len(data) > 0:
            # With dimension reset, openpyxl no longer pads rows
            max_width = max(len(data_row) for data_row in data)
            if min(len(data_row) for data_row in data) < max_width:
                empty_cell: List[Scalar] = [""]
                data = [
                    data_row + (max_width - len(data_row)) * empty_cell
                    for data_row in data
                ]

        return data
変わり始める
ここで直接暴力的にパンダスライブラリのソースファイルを変更します。(デバッグのみに使用して、バックアップと自分の作業環境を注意して保護します)
メインプログラムコード
main.pyを作成して、コードは比較的に簡単で、関連機能はすべて注釈を使って説明します。pd_read_.エクセル_プログレスは私が作成したコールバック関数です。コマンドラインでリアルタイムの読み込み進捗を出力します。もちろん、PYQT 5などのGUIプログラムを作成すれば、このコールバック関数でsignalをmain UIに送信し、プログレスバーや他のGUIスタイルを作成することもできます。

import pandas as pd
from datetime import datetime

'''
      
cur:         
tt:         
'''
def show_pd_read_excel_progress(cur, tt):
    #     
    progress = " {:.2f}%".format(cur/tt*100)
    #    
    bar = " ".join("" for _ in range(int(cur/tt*100/10)))
    #     
    print("\r  :" + bar + progress, end="", flush=True)

#       
t = datetime.now()
#     excel
print("pd.read_excel: test_4.xlsx...")
xl_data = pd.read_excel("test_4.xlsx", callback=show_pd_read_excel_progress)
#   excel   
print(xl_data.head())
print("
") # print("Time spent:", datetime.now()-t)
パンダスのソースコードを修正します
もう一度自分で観察してみます。pd.read_にいます。excelメソッドのパラメータにcalbackパラメータが追加されました。このパラメータはオリジナルread(u)です。excelの方法にはないです。だから、パンダスのソースコードを処理したいです。このソースは…/panedas/io/excel/uです。base.pyでは、pycharmでcontrolを押しながらreadをクリックします。excelは速くジャンプできます。ここでは一つのパラメーターのcalbackを追加しました。標準値はNone.下方io.parseと同じです。calbackパラメータをExcell File類に伝えます。

def read_excel(
    io,
    sheet_name=0,
    header=0,
    names=None,
    index_col=None,
    usecols=None,
    squeeze=False,
    dtype=None,
    engine=None,
    converters=None,
    true_values=None,
    false_values=None,
    skiprows=None,
    nrows=None,
    na_values=None,
    keep_default_na=True,
    na_filter=True,
    verbose=False,
    parse_dates=False,
    date_parser=None,
    thousands=None,
    comment=None,
    skipfooter=0,
    convert_float=True,
    mangle_dupe_cols=True,
    storage_options: StorageOptions = None,
    callback = None, #   callback  
):

    should_close = False
    if not isinstance(io, ExcelFile):
        should_close = True
        io = ExcelFile(io, storage_options=storage_options, engine=engine)
    elif engine and engine != io.engine:
        raise ValueError(
            "Engine should not be specified when passing "
            "an ExcelFile - ExcelFile already has the engine set"
        )

    try:
        data = io.parse(
            sheet_name=sheet_name,
            header=header,
            names=names,
            index_col=index_col,
            usecols=usecols,
            squeeze=squeeze,
            dtype=dtype,
            converters=converters,
            true_values=true_values,
            false_values=false_values,
            skiprows=skiprows,
            nrows=nrows,
            na_values=na_values,
            keep_default_na=keep_default_na,
            na_filter=na_filter,
            verbose=verbose,
            parse_dates=parse_dates,
            date_parser=date_parser,
            thousands=thousands,
            comment=comment,
            skipfooter=skipfooter,
            convert_float=convert_float,
            mangle_dupe_cols=mangle_dupe_cols,
            callback = callback, #   callback  
        )
    finally:
        # make sure to close opened file handles
        if should_close:
            io.close()
    return data
... #     
Excel File類を見てください。base.pyのコードです。このクラスはファイルの種類によってエンジンを選択します。私が読んだのはxlsxファイルです。だからopenpyxlにジャンプして、すべてのパラメータを渡すので、このクラスは処理しません。下に_にジャンプします。openpyxl.pyでOpenpyxlReader類を見てください。このクラスはBaseExcel Reader類を継承します。basee.pyの中で、だからやはり帰ってBaseExcel Readerを見て、そしてパラメーターを改正して、calbackを増加します。

def parse(
        self,
        sheet_name=0,
        header=0,
        names=None,
        index_col=None,
        usecols=None,
        squeeze=False,
        dtype=None,
        true_values=None,
        false_values=None,
        skiprows=None,
        nrows=None,
        na_values=None,
        verbose=False,
        parse_dates=False,
        date_parser=None,
        thousands=None,
        comment=None,
        skipfooter=0,
        convert_float=True,
        mangle_dupe_cols=True,
        callback = None, #   callback  
        **kwds,
    ):
... #     

for asheetname in sheets:
            if verbose:
                print(f"Reading sheet {asheetname}")

            if isinstance(asheetname, str):
                sheet = self.get_sheet_by_name(asheetname)
            else:  # assume an integer if not a string
                sheet = self.get_sheet_by_index(asheetname)

            data = self.get_sheet_data(sheet, convert_float, callback) #   callback   get_sheet_data  
            usecols = maybe_convert_usecols(usecols)
... #     
はい、やっとポイントになりました。get_までジャンプします。sheet_dataメソッドは、対応する修正(メソッドパラメータ、行の合計数を取得し、コールバック関数を呼び出す)を行います。非常に明確なアイデアは、操作を通じて、ついに千里はるばるcalbackを1つの層に転送しましたので、行の読み取りでexcelを呼び出すことができます。

def get_sheet_data(self, sheet, convert_float: bool, callback) -> List[List[Scalar]]: #       callback
        # GH 39001
        # Reading of excel file depends on dimension data being correct but
        # writers sometimes omit or get it wrong
        import openpyxl
				#   sheet    
        max_row = sheet.max_row
        print("sheet_max_row:", sheet.max_row)

        version = LooseVersion(get_version(openpyxl))

        # There is no good way of determining if a sheet is read-only
        # https://foss.heptapod.net/openpyxl/openpyxl/-/issues/1605
        is_readonly = hasattr(sheet, "reset_dimensions")

        if version >= "3.0.0" and is_readonly:
            sheet.reset_dimensions()

        data: List[List[Scalar]] = []
        last_row_with_data = -1
        for row_number, row in enumerate(sheet.rows):
						#       
            if callback is not None:
                callback(row_number+1, max_row)
            converted_row = [self._convert_cell(cell, convert_float) for cell in row]
            if not all(cell == "" for cell in converted_row):
                last_row_with_data = row_number
            data.append(converted_row)

        # Trim trailing empty rows
        data = data[: last_row_with_data + 1]

        if version >= "3.0.0" and is_readonly and len(data) > 0:
            # With dimension reset, openpyxl no longer pads rows
            max_width = max(len(data_row) for data_row in data)
            if min(len(data_row) for data_row in data) < max_width:
                empty_cell: List[Scalar] = [""]
                data = [
                    data_row + (max_width - len(data_row)) * empty_cell
                    for data_row in data
                ]

        return data
テストを実行
main.pyを実行してください。効果は以下の通りです。リアルタイムで進捗機能が実現されており、読み取りにかかる時間が計算されます。csvやsqlなどを読み込むなら、猫を見ても大丈夫です。
在这里插入图片描述
最適化と応用
  • 前にも言ったように、直接にパンダスのソースコードを変更するのは非常に非科学的な操作です。これは既存のプログラミング環境を破壊し、ソースを他のマシンに換えると、再度変更しなければなりません。
  • も継承+書き換えを試みましたが、レベルが限られています。
  • printプログレスバーは非常に時間がかかります。もちろん、1行のexcelを読むごとにプログレスバーを更新する必要はありません。タイミング(例えば、毎秒1回のブラシ)や定量(n行毎、または1%のプログレスを1回更新する)が合理的です。
  • 大規模なデータを読み取る時、コールバック関数を頻繁に起動すると、効率が悪くなりますが、GUIプログラムや他の人に使う場合、リアルタイムの進捗状況があれば、必ずユーザー体験を改善します。
  • ここでは、パンdasでエクセルを読み込む際の読み込み進捗の実現に関する記事を紹介します。これまでの記事を検索したり、下記の関連記事を見たりしてください。これからもよろしくお願いします。