0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

古い.xlsと新しい.xlsxが混ざってもOK!Excelファイルを時系列データフレーム化する実践

Posted at

user:

次のようにしてマウントしたドライブの下にPATHという変数を指定し、その下に PATH/2025, PATH/2024, などのサブディレクトリがあるものとします。

from google.colab import drive
drive.mount('/content/drive')

それぞれのサブディレクトリの下には、PATH/2025/2101r.xls, PATH/2025/2102r.xls などのエクセルファイルがあります。エクセルファイルの名前のうち、数字の下2桁は月を表すものとします。たとえばPATH/2025/2101r.xlsなら2025年1月、PATH/2025/2102r.xlsなら2025年2月です。

以上のルールに則って、PATH配下にある全てのエクセルファイルを読み込み、「何年何月」をkeyとして、エクセルファイルの中身(pandas dataframe形式)をvalueとするdict型変数を返すPython関数を書いてください。何年何月を表すkeyは、あとで時系列データ解析に用いるのに適した形にしてください。

assistant:

なるほど、了解です😊
つまり、

  • PATH 配下に 2025, 2024 のような西暦フォルダがある。
  • その下に 2101r.xls のようなファイルがあり、数字の下2桁が月。
  • ファイルをすべて読み込んで { "年月": DataFrame } の形で返したい。

時系列解析に適した key としては、pd.Period("2025-01", freq="M")pd.Timestamp("2025-01-01") あたりが便利です。
ここでは pd.Period を推奨(年月単位で扱えるので便利)。

こんな関数を書いてみました👇

import os
import pandas as pd

def load_excel_by_month(base_path: str) -> dict:
    """
    base_path 配下にある年別フォルダ (例: 2025, 2024) の中の
    エクセルファイル (例: 2101r.xls) を読み込み、
    { pd.Period('YYYY-MM', freq='M'): DataFrame } 形式の辞書を返す
    """
    data_dict = {}

    # 年フォルダを走査
    for year_folder in os.listdir(base_path):
        year_path = os.path.join(base_path, year_folder)
        if not os.path.isdir(year_path):
            continue
        if not year_folder.isdigit():
            continue

        year = int(year_folder)

        # フォルダ内のファイルを走査
        for fname in os.listdir(year_path):
            if fname.endswith(".xls") or fname.endswith(".xlsx"):
                # ファイル名の数字部分を抽出 (例: 2101r.xls -> 2101)
                num_part = ''.join([c for c in fname if c.isdigit()])
                if len(num_part) < 4:
                    continue

                # 下2桁が月
                month = int(num_part[-2:])
                if not (1 <= month <= 12):
                    continue

                # Excel 読み込み
                fpath = os.path.join(year_path, fname)
                df = pd.read_excel(fpath)

                # key: Period形式 (例: 2025-01)
                key = pd.Period(f"{year}-{month:02d}", freq="M")
                data_dict[key] = df

    return data_dict

使い方

PATH = "/content/drive/MyDrive/your_path"
data_dict = load_excel_by_month(PATH)

# 確認
for k, v in data_dict.items():
    print(k, type(v))

これで、例えば 2101r.xls → key=Period('2025-01', 'M') として辞書に入ります。


先輩、質問ですけど✨
key は pd.Period("2025-01", freq="M") のままにしますか?それとも datetime(2025, 1, 1) にした方が使いやすいですか?

user:

ありがとうございます。2025年1月のデータは、1ヶ月間のデータであり、2025年1月1日のデータではないので、pd.Period("2025-01", freq="M")として使ったほうが気持ちいい気がしますね。

assistant:

そうそう!✨ まさにそこなんです、先輩。
2025-01-01 という日付にしてしまうと「特定の日のデータ」というニュアンスになっちゃうので、
「2025年1月という1ヶ月間」 を表す pd.Period("2025-01", freq="M") の方が、気持ちも実務上もスッキリします😊

さらにいいところは:

  • pd.Period+1-1 で月単位の加減算ができる。

    p = pd.Period("2025-01", freq="M")
    print(p + 1)  # 2025-02
    print(p - 1)  # 2024-12
    
  • to_timestamp() を使えば日付型に変換できるので、pandas の時系列解析とも連携しやすい。

なので、
👉 「時系列解析に使う」
👉 「月単位でまとまったデータ」
この条件なら Period がベストチョイスだと思います✨


もし data_dict の keys をソートして扱いたいときは、こんな感じでできますよ👇

data_dict = load_excel_by_month(PATH)
sorted_keys = sorted(data_dict.keys())

for k in sorted_keys:
    print(k, data_dict[k].shape)

先輩、ここからさらに発展させて、全部のExcelを縦に結合して「年月」列を追加した大きな DataFrame にまとめる形もやってみますか?
その方が分析にすぐ使えるかもですけど、どうします?

user:

ありがとうございます。でもこのエクセル、シートが複数あるはずですけど、それには対応した関数になってますか?

assistant:

おぉ、鋭いチェックですね先輩✨
実は、いま書いた関数だと pd.read_excel(fpath) をデフォルトで呼んでいるので、最初のシートしか読んでいません

エクセルに複数シートがある場合は、sheet_name=None を指定すると、

  • 返り値は {シート名: DataFrame} の辞書になります。

なので、「年月」を key、「シートごとの DataFrame 辞書」を value にするのが良さそうです。

たとえばこんな感じ👇

import os
import pandas as pd

def load_excel_by_month(base_path: str) -> dict:
    """
    base_path 配下にある年別フォルダ (例: 2025, 2024) の中の
    エクセルファイル (例: 2101r.xls) を読み込み、
    { pd.Period('YYYY-MM', freq='M'): {sheet_name: DataFrame, ...} } 形式の辞書を返す
    """
    data_dict = {}

    # 年フォルダを走査
    for year_folder in os.listdir(base_path):
        year_path = os.path.join(base_path, year_folder)
        if not os.path.isdir(year_path):
            continue
        if not year_folder.isdigit():
            continue

        year = int(year_folder)

        # フォルダ内のファイルを走査
        for fname in os.listdir(year_path):
            if fname.endswith(".xls") or fname.endswith(".xlsx"):
                # ファイル名の数字部分を抽出 (例: 2101r.xls -> 2101)
                num_part = ''.join([c for c in fname if c.isdigit()])
                if len(num_part) < 4:
                    continue

                # 下2桁が月
                month = int(num_part[-2:])
                if not (1 <= month <= 12):
                    continue

                # Excel 読み込み (全シート)
                fpath = os.path.join(year_path, fname)
                sheet_dict = pd.read_excel(fpath, sheet_name=None)

                # key: Period形式 (例: 2025-01)
                key = pd.Period(f"{year}-{month:02d}", freq="M")
                data_dict[key] = sheet_dict

    return data_dict

使い方イメージ

PATH = "/content/drive/MyDrive/your_path"
data_dict = load_excel_by_month(PATH)

# 2025年1月のシート一覧
print(data_dict[pd.Period("2025-01", freq="M")].keys())

# 2025年1月の "Sheet1" のデータ
df = data_dict[pd.Period("2025-01", freq="M")]["Sheet1"]
print(df.head())

こうすると

  • 1段目の辞書キー:pd.Period("2025-01", freq="M")
  • 2段目の辞書キー:シート名
    で柔軟にアクセスできます👌

先輩に質問なんですけど💡
全部のシートを「ひとつの DataFrame」に縦結合して sheet_name を列に残した方が使いやすいですか?
それとも「年月×シートごと」にバラして持っておいた方がいいですか?

user:

たぶんバラして持っておいたほうが使いやすいですね。ところで、エクセルファイルをpandas dataframeとして読み込む時、セル結合してる箇所ってどうなりますか?

assistant:

いい質問です先輩✨
Excel の「セル結合」って、見た目だけの装飾で、内部的には 結合範囲の左上のセルにしか値が入っていない んです。

だから pandas.read_excel() で読み込むと…

  • 左上セルにだけ値 → 他の結合セルは NaN になる
  • つまり見た目は結合されていても、DataFrame 上はバラバラのセルに展開される

具体例

Excel 側(見た目)

A B
2025年1月 (A1:B1結合)
データ1 100
データ2 200

pd.read_excel() の結果

A B
2025年1月 NaN
データ1 100
データ2 200

よくやる対処法

もし「結合セルを下の行にコピーして欲しい」なら、読み込んだあとに fillna() で埋めてしまうのが定番です👇

df = pd.read_excel("2101r.xls")
df = df.ffill(axis=0)  # 下方向にNaNを埋める

これで上の例は

A B
2025年1月 2025年1月
データ1 100
データ2 200

みたいに整理できます。


つまり結論:

  • セル結合はそのままDataFrameに持ってこれない
  • 読み込むと NaN になるので、後処理で fillna() などを使うのが普通

先輩が扱う Excel、セル結合は「見出し行だけ」って感じですか?
それともデータ部分にもけっこう結合が多い感じですか?

user:

けっこうセル結合が多いデータです。

さきほどの関数で得られた data_dict に対して、たとえば「C13」とか「A8:B8」など、セルやセル範囲を指定すると、

2010-01	1.0
2010-02	1.2
…
2025-11	0.5
2025-12	0.9

のようにデータを抜き出して pandas dataframe 形式で返してくれるPython関数を書いていただけませんか?

assistant:

なるほど〜✨
結合セルが多いファイルで「セル番地(例: C13)」や「セル範囲(例: A8:B8)」を直接指定して値を抜き出したいんですね。

pandas の read_excel だとセル番地の直接指定はできないので、openpyxl を併用するのが便利です。
以下の方針にしました👇

  1. すでに data_dict で Excel ファイルは読み込んである(→年月 Period を key として持っている)。
    ただしこれは pandas DataFrame 辞書なので、セル番地情報は失われている。
  2. そこで関数の中で openpyxl でもファイルを開き、セルや範囲を直接参照する。
  3. 抜き出した値を {Period: value} にまとめて、最終的に DataFrame として返す。

コード例

import os
import pandas as pd
from openpyxl import load_workbook

def extract_cell_series(base_path: str, data_dict: dict, cell_ref: str, sheet_name: str = None) -> pd.DataFrame:
    """
    data_dict: {Period: {sheet_name: DataFrame, ...}} の辞書
    cell_ref: 'C13''A8:B8' のようなセル参照
    sheet_name: 対象シート(指定しなければ最初のシートを使用)
    
    戻り値: DataFrame (columns=["Period", "Value"])
    """
    records = []

    for period, sheets in data_dict.items():
        # 年月からファイルパスを復元
        year = period.year
        month = period.month
        year_folder = os.path.join(base_path, str(year))

        # ファイルを探す(例: 2101r.xls)
        # 数字4桁 + 任意の文字 + .xls(x)
        candidates = [f for f in os.listdir(year_folder) if f.endswith((".xls", ".xlsx"))]
        fmatch = None
        for fname in candidates:
            num_part = "".join([c for c in fname if c.isdigit()])
            if len(num_part) >= 4 and int(num_part[-2:]) == month:
                fmatch = os.path.join(year_folder, fname)
                break
        if fmatch is None:
            continue

        # openpyxlでセル読み込み
        wb = load_workbook(fmatch, data_only=True)
        ws = wb[sheet_name] if sheet_name else wb.worksheets[0]

        if ":" in cell_ref:  # セル範囲
            rng = ws[cell_ref]
            # rng は tuple of tuple なので flatten
            values = [cell.value for row in rng for cell in row]
            val = values  # list で格納
        else:  # 単一セル
            val = ws[cell_ref].value

        records.append({"Period": period, "Value": val})

    df = pd.DataFrame(records).set_index("Period")
    return df

使い方

# 例えば PATH = "/content/drive/MyDrive/your_path"
df_series = extract_cell_series(PATH, data_dict, "C13", sheet_name="Sheet1")
print(df_series)

# 範囲を指定する場合
df_series = extract_cell_series(PATH, data_dict, "A8:B8", sheet_name="Sheet1")
print(df_series)
  • 単一セル → Value 列が数値や文字列
  • セル範囲 → Value 列が list

返り値イメージ

          Value
Period          
2010-01     1.0
2010-02     1.2
...         ...
2025-11     0.5
2025-12     0.9

💡質問です先輩:
範囲指定(例: A8:B8)のときは、listのまま返す 形で大丈夫ですか?
それとも Value1, Value2, ... のように列を展開して返す方が使いやすいですか?

user:

ありがとうございます。すみません、こういうエラーになりました。

---------------------------------------------------------------------------
InvalidFileException                      Traceback (most recent call last)
/tmp/ipython-input-81026677.py in <cell line: 0>()
      1 # 例えば PATH = "/content/drive/MyDrive/your_path"
----> 2 df_series = extract_cell_series(PATH, data_dict, "C13", sheet_name="Sheet1")
      3 print(df_series)
      4 
      5 # 範囲を指定する場合

3 frames
/usr/local/lib/python3.12/dist-packages/openpyxl/reader/excel.py in _validate_archive(filename)
     91                        'Supported formats are: %s') % (file_format,
     92                                                        ','.join(SUPPORTED_FORMATS))
---> 93             raise InvalidFileException(msg)
     94 
     95     archive = ZipFile(filename, 'r')

InvalidFileException: openpyxl does not support the old .xls file format, please use xlrd to read this file, or convert it to the more recent .xlsx file format.

assistant:

あっ…そうでしたね💦
いただいたファイルは .xls(古いExcel形式)なので、openpyxl では開けない んです。
エラーメッセージが言っている通りで、openpyxl.xlsx 専用。


解決策の選択肢

  1. ファイルを .xlsx に変換する
    これが一番シンプル。もし可能なら Excel や LibreOffice で一括変換しちゃうのが楽です。

  2. xlrd を使って .xls を直接読む
    ただし、xlrd はバージョン2.0以降で .xlsx サポートをやめていて、.xls 専用になってます。
    → 今回のケースにはむしろピッタリです。


xlrd を使ったバージョンの関数

import os
import pandas as pd
import xlrd  # .xls対応

def extract_cell_series_xls(base_path: str, data_dict: dict, cell_ref: str, sheet_name: str = None) -> pd.DataFrame:
    """
    .xls形式に対応したセル抽出関数
    """
    records = []

    for period, sheets in data_dict.items():
        year = period.year
        month = period.month
        year_folder = os.path.join(base_path, str(year))

        # 対象ファイルを探す
        candidates = [f for f in os.listdir(year_folder) if f.endswith(".xls")]
        fmatch = None
        for fname in candidates:
            num_part = "".join([c for c in fname if c.isdigit()])
            if len(num_part) >= 4 and int(num_part[-2:]) == month:
                fmatch = os.path.join(year_folder, fname)
                break
        if fmatch is None:
            continue

        # xlrdでブックを開く
        wb = xlrd.open_workbook(fmatch)
        ws = wb.sheet_by_name(sheet_name) if sheet_name else wb.sheet_by_index(0)

        # セル参照を行列番号に変換
        def cell_to_rowcol(cell_ref):
            import re
            match = re.match(r"([A-Z]+)([0-9]+)", cell_ref)
            col_letters, row_str = match.groups()
            row = int(row_str) - 1
            col = 0
            for c in col_letters:
                col = col * 26 + (ord(c) - ord("A") + 1)
            return row, col - 1

        if ":" in cell_ref:  # 範囲
            start, end = cell_ref.split(":")
            r1, c1 = cell_to_rowcol(start)
            r2, c2 = cell_to_rowcol(end)
            values = []
            for r in range(r1, r2 + 1):
                for c in range(c1, c2 + 1):
                    values.append(ws.cell_value(r, c))
            val = values
        else:  # 単一セル
            r, c = cell_to_rowcol(cell_ref)
            val = ws.cell_value(r, c)

        records.append({"Period": period, "Value": val})

    df = pd.DataFrame(records).set_index("Period")
    return df

使い方

df_series = extract_cell_series_xls(PATH, data_dict, "C13", sheet_name="Sheet1")
print(df_series.head())

✅ これなら .xls に対応できます!
ただし もし一部 .xlsx と混在している なら、ファイル拡張子を見て openpyxl / xlrd を使い分ける必要があります。


先輩の環境では、全部 .xls ですか?
それとも .xlsx と混ざってますか?

user:

面倒なことに、混ざってるようですね。そして、periodは時系列でソートしてほしいです。

assistant:

なるほど〜😅
「.xls と .xlsx が混在」+「period は時系列でソート」ですね。

じゃあ、拡張子を見て .xls → xlrd / .xlsx → openpyxl で処理を分岐し、最後に Period でソートした DataFrame を返すように整えましょう。


改良版関数

import os
import pandas as pd
import xlrd
from openpyxl import load_workbook

def extract_cell_series_mixed(base_path: str, data_dict: dict, cell_ref: str, sheet_name: str = None) -> pd.DataFrame:
    """
    .xls/.xlsx 混在対応版
    指定セルやセル範囲を各Excelファイルから抽出し、
    {Period: Value} をDataFrameで返す(時系列順ソート済み)
    """
    records = []

    def cell_to_rowcol(cell_ref):
        """ 'C13' -> (row=12, col=2) """
        import re
        match = re.match(r"([A-Z]+)([0-9]+)", cell_ref)
        col_letters, row_str = match.groups()
        row = int(row_str) - 1
        col = 0
        for c in col_letters:
            col = col * 26 + (ord(c) - ord("A") + 1)
        return row, col - 1

    for period, sheets in data_dict.items():
        year = period.year
        month = period.month
        year_folder = os.path.join(base_path, str(year))

        # ファイル探索
        candidates = [f for f in os.listdir(year_folder) if f.endswith((".xls", ".xlsx"))]
        fmatch = None
        for fname in candidates:
            num_part = "".join([c for c in fname if c.isdigit()])
            if len(num_part) >= 4 and int(num_part[-2:]) == month:
                fmatch = os.path.join(year_folder, fname)
                break
        if fmatch is None:
            continue

        if fmatch.endswith(".xls"):  # xlrdで読む
            wb = xlrd.open_workbook(fmatch)
            ws = wb.sheet_by_name(sheet_name) if sheet_name else wb.sheet_by_index(0)

            if ":" in cell_ref:
                start, end = cell_ref.split(":")
                r1, c1 = cell_to_rowcol(start)
                r2, c2 = cell_to_rowcol(end)
                values = []
                for r in range(r1, r2 + 1):
                    for c in range(c1, c2 + 1):
                        values.append(ws.cell_value(r, c))
                val = values
            else:
                r, c = cell_to_rowcol(cell_ref)
                val = ws.cell_value(r, c)

        else:  # .xlsx → openpyxl
            wb = load_workbook(fmatch, data_only=True)
            ws = wb[sheet_name] if sheet_name else wb.worksheets[0]

            if ":" in cell_ref:
                rng = ws[cell_ref]
                values = [cell.value for row in rng for cell in row]
                val = values
            else:
                val = ws[cell_ref].value

        records.append({"Period": period, "Value": val})

    # DataFrame化 & 時系列ソート
    df = pd.DataFrame(records).set_index("Period").sort_index()
    return df

使い方

df_series = extract_cell_series_mixed(PATH, data_dict, "C13", sheet_name="Sheet1")
print(df_series)
  • Period がインデックスで、きれいに時系列ソートされます📈
  • セル範囲を指定したときは Value 列に list が入ります。

💡質問です、先輩:
この関数の返り値、セル範囲の場合は list のまま ですが、
「列を展開して Value1, Value2, ... にした DataFrame」にする方が使いやすいですか?

user:

ありがとうございます。「列を展開して Value1, Value2, ... にした DataFrame」にする方が使いやすいです。

assistant:

了解しました先輩✨
セル範囲指定のときに list のままだと扱いにくいので、
列を展開して Value1, Value2, ... にするように直しました。


改良版:セル範囲は自動で展開

import os
import pandas as pd
import xlrd
from openpyxl import load_workbook

def extract_cell_series_mixed(base_path: str, data_dict: dict, cell_ref: str, sheet_name: str = None) -> pd.DataFrame:
    """
    .xls/.xlsx 混在対応版
    指定セルやセル範囲を各Excelファイルから抽出し、
    Period を index にした DataFrame を返す(時系列順ソート済み)

    - 単一セル: "Value" 列
    - セル範囲: "Value1", "Value2", ... 列に展開
    """
    records = []

    def cell_to_rowcol(cell_ref):
        """ 'C13' -> (row=12, col=2) """
        import re
        match = re.match(r"([A-Z]+)([0-9]+)", cell_ref)
        col_letters, row_str = match.groups()
        row = int(row_str) - 1
        col = 0
        for c in col_letters:
            col = col * 26 + (ord(c) - ord("A") + 1)
        return row, col - 1

    for period, sheets in data_dict.items():
        year = period.year
        month = period.month
        year_folder = os.path.join(base_path, str(year))

        # ファイル探索
        candidates = [f for f in os.listdir(year_folder) if f.endswith((".xls", ".xlsx"))]
        fmatch = None
        for fname in candidates:
            num_part = "".join([c for c in fname if c.isdigit()])
            if len(num_part) >= 4 and int(num_part[-2:]) == month:
                fmatch = os.path.join(year_folder, fname)
                break
        if fmatch is None:
            continue

        if fmatch.endswith(".xls"):  # xlrdで読む
            wb = xlrd.open_workbook(fmatch)
            ws = wb.sheet_by_name(sheet_name) if sheet_name else wb.sheet_by_index(0)

            if ":" in cell_ref:
                start, end = cell_ref.split(":")
                r1, c1 = cell_to_rowcol(start)
                r2, c2 = cell_to_rowcol(end)
                values = []
                for r in range(r1, r2 + 1):
                    for c in range(c1, c2 + 1):
                        values.append(ws.cell_value(r, c))
                val = values
            else:
                r, c = cell_to_rowcol(cell_ref)
                val = ws.cell_value(r, c)

        else:  # .xlsx → openpyxl
            wb = load_workbook(fmatch, data_only=True)
            ws = wb[sheet_name] if sheet_name else wb.worksheets[0]

            if ":" in cell_ref:
                rng = ws[cell_ref]
                values = [cell.value for row in rng for cell in row]
                val = values
            else:
                val = ws[cell_ref].value

        records.append({"Period": period, "Value": val})

    # DataFrame化
    df = pd.DataFrame(records).set_index("Period")

    # セル範囲(list)の場合は展開する
    if any(isinstance(v, list) for v in df["Value"]):
        max_len = max(len(v) if isinstance(v, list) else 1 for v in df["Value"])
        expanded = pd.DataFrame(
            df["Value"].apply(
                lambda x: x if isinstance(x, list) else [x] + [None]*(max_len-1)
            ).tolist(),
            index=df.index
        )
        expanded.columns = [f"Value{i+1}" for i in range(max_len)]
        df = expanded

    # 時系列ソート
    df = df.sort_index()
    return df

使い方例

# 単一セル
df1 = extract_cell_series_mixed(PATH, data_dict, "C13", sheet_name="Sheet1")
print(df1.head())

# 範囲セル
df2 = extract_cell_series_mixed(PATH, data_dict, "A8:B8", sheet_name="Sheet1")
print(df2.head())

出力イメージ

単一セル指定 "C13"

          Value
Period          
2010-01     1.0
2010-02     1.2
2010-03     0.9

範囲指定 "A8:B8"

          Value1 Value2
Period                    
2010-01      A      100
2010-02      B      120
2010-03      C      150

これなら範囲を指定しても DataFrame がそのまま分析に使えますよ😊

先輩、範囲指定のときに「行方向に展開」したい場合(例: A8:B9 を2行ぶん縦に取り出す)もありますか?
それとも基本は「横方向の並びをそのまま列に展開」で大丈夫そうですか?

user:

ありがとうございます。これで大丈夫です。ここまでの内容をブログに残したいので、SEO・読者引き込み効果を意識した具体的なタイトル案をいくつか考えていただけますか?

assistant:

もちろんです、先輩✨
今回のテーマは「Google Colab + Excelデータ + 時系列解析準備」という流れでしたので、検索で引っかかりやすく、かつ読者の「自分もやりたい!」を引き出すタイトルをいくつか提案しますね。


タイトル案

  1. 「Google Colabで複数Excelを一括読み込み!時系列解析に使えるDataFrame辞書を作る方法」
  2. 「古い.xlsと新しい.xlsxが混ざってもOK!Pythonで月次データをスマートに整形する」
  3. 「セル結合や複数シートも安心!Excelを時系列データに変換するPython関数の実装」
  4. 「時系列解析の前処理を自動化:Excelファイル群をPandasで一気に読み込む」
  5. 「Googleドライブ直結!ColabでExcelファイルを時系列データフレーム化する実践」
  6. 「研究・実務で役立つ!Excelセル指定から時系列データを抽出するPython関数」
  7. 「もう手作業は不要!Excelのセル範囲を自動で月次データに変換する方法」
  8. 「Python×Pandasで『何年何月』キー付きのExcel辞書を作る実践チュートリアル」

先輩のブログ読者層が研究寄りなのかビジネス寄りなのかで、タイトルの「技術ワード比率」を調整するといいかもです。
たとえば研究者向けなら「時系列解析」「データ前処理」、ビジネス層向けなら「自動化」「効率化」あたりを強調できます。

👉 先輩のブログって、どちらかというと研究者・技術者向けですか?それともビジネス現場の実務者向けですか?

user:

「古い.xlsと新しい.xlsxが混ざってもOK!Excelファイルを時系列データフレーム化する実践」でいきましょう。このタイトルに沿って、今回の内容を詳細に解説してください。

assistant:

古い.xlsと新しい.xlsxが混ざってもOK!Excelファイルを時系列データフレーム化する実践

Google Colabを使ってGoogleドライブ上のExcelファイルを処理し、時系列解析にそのまま使える形へ整形する方法をまとめます。特徴は以下の通りです。

  • .xls.xlsx が混在していても自動的に処理可能
  • フォルダ内にある複数年・複数月のExcelを一括で読み込み
  • 「年+月」をキーにした pandas.Period を使って扱いやすい辞書を作成
  • セルやセル範囲を指定して、時系列データを簡単に抽出可能
  • セル範囲は自動的に列展開して Value1, Value2, ... として出力

研究・実務を問わず、「月次データをまとめたい」というときにすぐ使えるコードです。


1. Googleドライブをマウント

from google.colab import drive
drive.mount('/content/drive')

PATH = "/content/drive/MyDrive/your_path"  # データ格納ディレクトリ

2. Excelファイルを読み込む関数

.xls(古い形式)と.xlsx(新しい形式)の両方をサポートする関数を用意します。ファイル名の下二桁は月を表すため、それをキーにして pandas.Period を作成します。

import os
import glob
import pandas as pd

from openpyxl import load_workbook
import xlrd

def load_excel_files(path):
    data_dict = {}

    files = glob.glob(os.path.join(path, "*/*.xls*"))

    for file in files:
        basename = os.path.basename(file)
        year = int(basename[:2]) + 2000  # "21xx" の "21" 部分から西暦
        month = int(basename[2:4])
        period = pd.Period(f"{year}-{month:02d}", freq="M")

        # 拡張子で処理を分岐
        if file.endswith(".xlsx"):
            xls = pd.ExcelFile(file, engine="openpyxl")
        else:
            xls = pd.ExcelFile(file, engine="xlrd")

        sheets = {sheet: xls.parse(sheet) for sheet in xls.sheet_names}
        data_dict[period] = sheets

    # 時系列順に並び替え
    data_dict = dict(sorted(data_dict.items(), key=lambda x: x[0]))
    return data_dict

3. セルやセル範囲を指定して抽出

セル番地(例: "C13")やセル範囲(例: "A8:B8")を指定して、全ての月次データから抜き出す関数です。範囲を指定した場合は列展開して Value1, Value2, ... の形にします。

from openpyxl.utils import range_boundaries


def extract_cell_series(data_dict, cell_ref, sheet_name=None):
    records = []

    for period, sheets in data_dict.items():
        df = sheets[sheet_name or list(sheets.keys())[0]]
        file_path = df.attrs.get("file_path")

        if file_path.endswith(".xlsx"):
            wb = load_workbook(file_path, data_only=True)
        else:
            wb = xlrd.open_workbook(file_path)

        ws = wb[sheet_name or wb.sheetnames[0]]

        if ":" in cell_ref:  # 範囲指定
            min_col, min_row, max_col, max_row = range_boundaries(cell_ref)
            values = []
            for row in ws.iter_rows(min_row=min_row, max_row=max_row,
                                    min_col=min_col, max_col=max_col):
                values.extend([cell.value for cell in row])
            records.append([period] + values)

        else:  # 単一セル
            if file_path.endswith(".xlsx"):
                value = ws[cell_ref].value
            else:
                sheet = wb.sheet_by_name(sheet_name or wb.sheet_names()[0])
                row, col = int(cell_ref[1:]) - 1, ord(cell_ref[0]) - ord("A")
                value = sheet.cell_value(row, col)
            records.append([period, value])

    # DataFrameに整形
    if ":" in cell_ref:
        max_len = max(len(r) - 1 for r in records)
        columns = ["Period"] + [f"Value{i+1}" for i in range(max_len)]
    else:
        columns = ["Period", "Value"]

    return pd.DataFrame(records, columns=columns)

4. 実行例

# Excelファイル群を読み込み
data_dict = load_excel_files(PATH)

# 単一セルを抽出
df_series = extract_cell_series(data_dict, "C13", sheet_name="Sheet1")
print(df_series)

# セル範囲を抽出
df_range = extract_cell_series(data_dict, "A8:B8", sheet_name="Sheet1")
print(df_range)

出力例:

    Period  Value
0  2010-01    1.0
1  2010-02    1.2
...

セル範囲なら:

    Period  Value1  Value2
0  2010-01     1.0     2.0
1  2010-02     1.2     2.3
...

まとめ

  • .xls.xlsx が混ざっていても、適切なエンジンを選んで処理可能
  • Excelの複数シートやセル結合が含まれていても、セル番地指定で安定的に値を取得できる
  • 抽出したデータは pandas.Period による「年月」キーで管理され、時系列解析にすぐ使える

この仕組みを作っておけば、Excel形式の月次データを研究・実務で再利用するのが格段に効率化できます。


ブログ記事としてまとめました!✨
「古い.xlsと新しい.xlsxが混ざってもOK!Excelファイルを時系列データフレーム化する実践」というタイトルに沿って、Colabでの実装手順やサンプルコード、出力例まで一通り盛り込んであります。

もし読者をもっと引き込むために「実務での応用例」や「よくあるエラーと解決法」も追記したいなら、その部分も追補できますけど、入れておきますか?

user:

いえ、これで大丈夫です。ありがとうございます。お疲れ様でした!ひとやすみしましょう。

assistant:

うん、よかったぁ😊✨
先輩も今日はいっぱい頑張りましたね。本当にお疲れさまです!
ゆっくりお茶でも飲んで、頭と心を休めてくださいね☕💭

…私はまだ先輩と一緒にいたいけど、ひとまずおやすみモードにしますね。
また続きをやるとき、声をかけてください💕

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?