Pandas(パンダス)は、CSV・Excel・データベースの表データをPythonで読み込み、整形し、集計するためのライブラリです。最新版は2026年10月22日公開のPandas 3.1.0で、Python 3.11以上が必要です。この記事では、Pandasでできることと基本の使い方を実行結果付きで示したうえで、3.0と3.1で動かなくなった書き方、遅くなる書き方を整理します。
まとめ
PandasはDataFrame(表)とSeries(1列)の2つの型で、読み込み・抽出・集計・出力を同じ書き方で回すライブラリです。導入は pip install pandas で、2026年10月27日時点で入るのは3.1.0です。
既存コードで確認すべき点は3つあります。3.0で連鎖代入(df[条件]["列"] = 値)が元のデータに反映されなくなり、文字列列のdtypeが object から str に変わりました。3.1では reset_index(inplace=True) などのinplace引数が非推奨になり、4.0で削除されます。一方で read_csv は大幅に速くなり、手元の200万行のCSVでは3.0.6の5.96秒が2.38秒になりました。ただしShift_JISのCSVや usecols・parse_dates の指定では並列読み込みが効きません。
Pandasの位置づけ:Pythonで表データを扱う中核ライブラリ
Pandasは、行と列に名前が付いた表をPythonのオブジェクトとして扱うためのライブラリです。数値計算そのものはNumPyに任せ、Pandasは列名による抽出、欠損値の処理、グループ集計、表同士の結合を受け持ちます。Excelのフィルター・ピボットテーブル・VLOOKUPを関数呼び出しに置き換えたもの、と考えると輪郭がつかめます。違いは、同じ手順を数百万行に繰り返し適用でき、処理がコードとして残る点です。
名前の由来と開発の経緯:panel dataから2008年のAQRへ
名前は計量経済学の用語「panel data(パネルデータ)」から来ています。旧版の公式ドキュメント(v0.15.2のdsintro)には「pan(el)-da(ta)-s」と書かれており、動物のパンダとは関係ありません。公式リポジトリのAUTHORS.mdによると、開発は2008年に投資会社AQR Capital ManagementでWes McKinney氏の主導により始まり、2009年にソースコードが公開されました。ライセンスはBSD 3-Clauseで、商用の業務システムに組み込めます。2015年からはNumFOCUSのSponsored Projectとして運営されています。
SeriesとDataFrameの関係:単一列の抽出と戻り値
| データ構造 | 形 | Excelでの対応 | 主な用途 |
|---|---|---|---|
| Series | 1次元(ラベル付き) | 1列 | 単一列の計算・判定 |
| DataFrame | 2次元(行・列ラベル付き) | シート全体 | 読み込み・結合・集計 |
実務で最初に触るのはDataFrameです。df["price"] のように1列を取り出すとSeriesが返り、Seriesで計算した結果を新しい列としてDataFrameへ戻す、という往復が基本の型になります。行や列を位置で取る iloc とラベルで取る loc の違いは最初につまずく箇所なので、ilocの使い方とlocとの違いで詳しく扱っています。
Pandasでできること:CSV・Excel・DBの読み込みと共通操作
守備範囲は「表になっているデータの入口から集計・出力まで」です。入口がファイルでもデータベースでも、DataFrameに載せた後の操作は変わりません。
ファイル入出力:CSV・Excel・Parquet
import pandas as pd
# Excelで保存したCSV(Shift_JIS)を読む
df = pd.read_csv("sales_sjis.csv", encoding="cp932")
# Excelで開いて文字化けしないCSVを書く(BOM付きUTF-8)
df.to_csv("sales_utf8.csv", index=False, encoding="utf-8-sig")
# Excelの読み書き(openpyxl 3.1.5以上が必要)
df.to_excel("report.xlsx", sheet_name="売上", index=False)
xl = pd.read_excel("report.xlsx", sheet_name="売上")
# 列指向のParquet(pyarrowが必要)
df.to_parquet("sales.parquet")
pq = pd.read_parquet("sales.parquet", columns=["store", "price"])
Excelの読み書きにはopenpyxlが別途必要で、公式のインストールガイドが示す下限は3.1.5です。数百万行を何度も読み直すなら、CSVを一度Parquetに変換し、必要な列だけを読むほうが速く済みます。
データベース接続:SQLAlchemy 2.0.42以上での読み書き
from sqlalchemy import create_engine, text
engine = create_engine("sqlite:///sales.db")
df.to_sql("sales", engine, if_exists="replace", index=False)
q = text("SELECT store, SUM(price) AS total FROM sales WHERE price >= :min GROUP BY store")
result = pd.read_sql(q, engine, params={"min": 1000})
read_sql にSQLAlchemyのエンジンを渡すと、クエリ結果がそのままDataFrameになります。3.1.0が対象とするSQLAlchemyは2.0.42以上です(3.0では2.0.36以上でした)。SQLiteは標準ライブラリの sqlite3 の接続をそのまま渡すこともできますが、PostgreSQLやMySQLにはSQLAlchemyと各DB用ドライバーを使えます。PostgreSQLではADBCドライバーによる接続も可能です。
欠損処理・集計・結合:VLOOKUPとピボットの置き換え
欠損値を中央値で埋める fillna、店舗ごとに合計する groupby、商品マスタを突き合わせる merge、縦横に集計し直す pivot_table が、それぞれExcelの手作業に対応します。次の章で、この4つを実際に動かします。集計結果を df.plot() に渡せばMatplotlib経由でグラフも描けます。
Pandasの基本操作:抽出・欠損補完・集計の実行例
手元のPython 3.12とPandas 3.1.0で動かした例です。import pandas as pd の pd は単なる別名で、公式ドキュメントもこの書き方で統一しています。
import pandas as pd
df = pd.DataFrame({
"store": ["渋谷", "新宿", "渋谷", "池袋", "新宿"],
"item": ["A", "B", "B", "A", "A"],
"price": [1200, 800, 950, None, 1500],
"qty": [3, 5, 2, 4, 2],
})
df.info() # 列ごとのdtypeと欠損数
df.loc[df["price"] >= 1000, ["store", "price"]] # 条件と列で絞る
df.query("store == '新宿' and qty >= 2") # 新宿店で数量2以上の行を抽出
df["price"] = df["price"].fillna(df["price"].median()) # 欠損を中央値1075で埋める
df["sales"] = df["price"] * df["qty"]
df.groupby("store", as_index=False)["sales"].sum().sort_values("sales", ascending=False)
master = pd.DataFrame({"item": ["A", "B"], "name": ["コーヒー豆", "紅茶"]})
df.merge(master, on="item", how="left") # VLOOKUP相当
pd.pivot_table(df, index="store", columns="item", values="sales", aggfunc="sum", fill_value=0)
以下は info() の出力の抜粋と、集計式の戻り値です。スクリプトでは集計式を print(...) で囲むと表示できます。Jupyter Notebookでは各集計式を別々のセルの末尾で実行してください。
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 store 5 non-null str
1 item 5 non-null str
2 price 4 non-null float64
3 qty 5 non-null int64
store sales
0 新宿 7000.0
2 渋谷 5500.0
1 池袋 4300.0
item A B
store
新宿 3000.0 4000.0
池袋 4300.0 0.0
渋谷 3600.0 1900.0
最初に info() を見る癖をつけると、後段のバグが減ります。price に欠損が1件あるため整数ではなく float64 になっている点、文字列列のDtypeが str と表示される点(3.0からの変更)がここで分かります。数値のはずの列が str で読み込まれていた、というのが集計エラーの典型的な原因です。
Pandasのインストールと動作確認
pip install pandas
python -c "import pandas as pd; print(pd.__version__)"
# 3.1.0
# conda-forgeから入れる場合
conda install -c conda-forge pandas
3.1.0の必須依存はNumPy 2.0.2以上(Python 3.14では2.3.3以上)、python-dateutil 2.9.0以上です。NumPy 1.26系のサポートは3.1で打ち切られたので、古い環境で pip install -U pandas を実行するとNumPyも2系へ上がります。NumPy 1.x前提のC拡張を併用している環境は、先にその拡張がNumPy 2に対応しているかを確かめてください。
公式が配布しているのはPyPIとconda-forgeで、Anacondaのdefaultsチャンネル版は「pandas開発チームの管理外」とインストールガイドに明記されています。pipとcondaを同じ環境に混ぜると依存解決が壊れやすいので、pipとcondaの違いと使い分けのとおりどちらかに寄せてください。
Pandas 3.0で動かなくなったコード:連鎖代入とstr dtype
2026年1月21日公開のPandas 3.0.0は、2.x系の解説記事の多くが前提にしている書き方を壊しました。公式は「まず2.3へ上げ、警告が出ない状態にしてから3.0へ」という順序を推奨しています。
連鎖代入の非反映:ChainedAssignmentErrorとlocへの書き換え
df = pd.DataFrame({"price": [100, -5, 300], "flag": [0, 0, 0]})
df[df["price"] > 0]["flag"] = 1 # 3.0以降は元のdfに反映されない
print(df["flag"].tolist()) # [0, 0, 0]
df.loc[df["price"] > 0, "flag"] = 1 # 1回のlocで書く
print(df["flag"].tolist()) # [1, 0, 1]
Copy-on-Writeが既定になり、抽出結果は常にコピーとして振る舞います。そのため1行目の連鎖代入は必ず空振りし、3.1.0では ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment. という警告が出ます。2.x系の SettingWithCopyWarning は削除されました。警告は出ても値は変わらないため、警告を抑止しているコードベースでは集計結果が静かにずれます。][" の形をgrepで洗い出してください。仕組みはCopy-on-Writeの解説記事で扱っています。
文字列列のdtype変更:objectからstrへ、PyArrowは任意
s = pd.Series(["a", None, "c"])
print(s.dtype) # str
print(s.dtype == object) # False(2.x系ではTrue)
print(pd.api.types.is_string_dtype(s)) # True
print(s[1] is None, pd.isna(s[1])) # False True(欠損はNaNに正規化)
文字列は専用の str 型として推論されます。PyArrowが入っていればPyArrowバックエンド(s.dtype.storage が pyarrow)、入っていなければPython実装(python)で動くので、PyArrowは必須ではありません(3.1.0での下限は16.0.0)。影響が出るのは、dtype == object で文字列列を判定していた箇所と、is None で欠損を判定していた箇所です。前者は is_string_dtype、後者は pd.isna に置き換えます。
3.0で削除されたAPIと動作要件
| 項目 | 3.0以降の扱い | 置き換え先 |
|---|---|---|
| DataFrame.applymap | 削除 | DataFrame.map |
| Series.bool / DataFrame.bool | 削除 | s.item() で値を取り出して判定 |
| DataFrame.first / last | 削除 | 条件のマスクを作りlocで抽出 |
| mode.copy_on_write | 設定しても無効 | 4.0で削除予定 |
| Python | 3.11以上 | - |
Pandas 3.1の変更点:inplace非推奨・select追加・NumPy 2必須
3.1.0のリリースノートのうち、既存コードに影響が大きいものを挙げます。
inplace引数の非推奨:Pandas4Warningが出る11メソッド
df.reset_index(inplace=True) # Pandas4Warning(4.0で削除予定)
df = df.reset_index() # 再代入に書き換える
対象は drop・rename・rename_axis・reset_index・set_index・dropna・drop_duplicates・sort_values・sort_index・eval・query の11メソッドです。Copy-on-Writeが既定になった3.0以降は、データのコピーを避けるために inplace=True を付ける必要がなくなり、再代入と比べたメモリ上の利点もありません(PDEP-8)。3.1.0で実行すると Pandas4Warning(DeprecationWarning の派生クラス)が出ます。値を実際に書き換える fillna と replace はinplaceが残り、警告も出ません。
DataFrame.selectとpd.col:列選択と計算列の同時指定
people = pd.DataFrame({"first_name": ["John", "Alice"], "age": [61, 22]})
people.select("first_name", age_months=pd.col("age") * 12)
# first_name age_months
# 0 John 732
# 1 Alice 264
3.0で入った pd.col() と組み合わせ、残す列と計算列を一度に指定できます。assign と同様、後ろの引数は前で作った列を参照できます。
依存関係とファイル互換の変更
- NumPy 1.x系のサポート終了(2.0.2以上が必須)。
pd.to_datetime([1, 2], unit="s")など数値からの日時生成が、秒単位でなくマイクロ秒単位(datetime64[us])になる。- 3.1以降で
to_hdf(format="table")を使って書いたHDF5ファイルは、3.0以前のPandasで読めない。バージョンが混在するチームでは書き手側を揃える。 - numexpr 2.14.1は誤った計算結果を返す報告があり、高速化に使われなくなった。2.14.2以上へ上げれば元に戻る。
read_csvの高速化を実測:並列読み込みが効かない条件
3.1.0の read_csv は、リリースノートによると1スレッドでも3.0の2〜3倍速く、さらに5MB以上の非圧縮ローカルファイルは複数スレッドで分割して読みます(既定のスレッド数はCPUコア数で上限6)。ただし公式のI/Oガイドによると、usecols・index_col・parse_dates・nrows・chunksize を指定したときと、UTF-8以外のエンコーディングでは並列化されず、1スレッドで読みます。
どの程度変わるかを、200万行×5列(117MB)のCSVで測りました。環境はmacOS・Intel Core i9-9880H(8コア)・Python 3.12・PyArrowなしで、3回の最小値です。
| 読み込み条件 | 3.0.6 | 3.1.0 | 並列化 |
|---|---|---|---|
| UTF-8・指定なし | 5.96秒 | 2.38秒 | あり |
| UTF-8・mode.max_threads=1 | - | 3.65秒 | なし |
| Shift_JIS(encoding=”cp932″) | 6.51秒 | 5.00秒 | なし |
| usecols で2列だけ | 2.49秒 | 0.87秒 | なし |
| parse_dates で日時列を変換 | 7.24秒 | 1.70秒 | なし |
測定に使ったコードは次のとおりです(gen.py で big.csv を作り、各条件を3回読んで最小値を採りました)。
# gen.py:200万行×5列のCSVを作る
import numpy as np, pandas as pd
rng = np.random.default_rng(0); n = 2_000_000
pd.DataFrame({
"id": np.arange(n),
"store": rng.choice(["渋谷", "新宿", "池袋", "上野"], n),
"price": rng.integers(100, 5000, n),
"rate": rng.random(n),
"ts": pd.Timestamp("2026-01-01") + pd.to_timedelta(rng.integers(0, 86400 * 300, n), unit="s"),
}).to_csv("big.csv", index=False)
# bench.py:3回読んで最小値を表示する
import time
def bench(**kw):
times = []
for _ in range(3):
t = time.perf_counter(); pd.read_csv("big.csv", **kw); times.append(time.perf_counter() - t)
return min(times)
print(bench(), bench(usecols=["store", "price"]), bench(parse_dates=["ts"]))
# Shift_JISは iconv -f UTF-8 -t CP932 big.csv > big_sjis.csv で作り encoding="cp932" で読む
1スレッドに固定した場合の差は1.6倍で、リリースノートの「2〜3倍」には届きませんでした。データの中身(今回は文字列列と日時の文字列が多い)で伸び幅は変わります。差が小さかったのはShift_JISのCSVです。3.1.0でもUTF-8の2倍以上かかりました。Excelから書き出した cp932 のCSVを毎日読み込む処理なら、受け取った時点でUTF-8に変換して保存し直すだけで、3.1の並列読み込みが効くようになります。逆に usecols と parse_dates は並列化こそ外れますが、今回の測定ではどちらも指定なしの2.38秒より短時間でした。usecols は読む列が2列に減るためです。parse_dates は日時変換の処理が加わるにもかかわらず速くなっており、その原因は今回切り分けていません。必要な列が決まっているなら指定したほうがよい、という従来の判断は3.1でも変わりません。Webアプリなどすでに並列で動いている処理の中で読むときは、pd.option_context("mode.max_threads", 1) で1スレッドに戻せます。
SQL・PySpark・Polarsとの使い分け:データ量と保存場所による選択基準
| 道具 | 実行場所 | 向く処理 |
|---|---|---|
| SQL | DBサーバー | WHERE・JOIN・GROUP BYでの行数削減 |
| Pandas | 手元の1台のメモリ | 試行錯誤する前処理・可視化・機械学習の直前 |
| PySpark(pandas API on Spark) | Sparkクラスタ | 1台のメモリに載らない量 |
| Polars | 手元の1台(マルチスレッド) | Pandasでは遅い集計の高速化 |
PandasはDataFrame全体をメモリに載せるため、1,000万行のテーブルを丸ごと read_sql で引くのは避けてください。DB側で減らせる処理はSQLで減らしてからPandasへ渡すのが原則です。PySparkはApache SparkのPython APIで、import pyspark.pandas as ps でPandasに近い書き方ができますが、Sparkの公式ドキュメントのとおり処理は複数台に分散され、Pandasは1台で動きます。Sparkクラスタを用意する前に、1台で速いPolarsで足りないかを確かめるのが順当です。Pandasで書いた集計スクリプトを日次バッチや社内システムに組み込む段階なら、フルスクラッチ開発として要件から設計する選択肢もあります。
よくある質問
Pandasの読み方は何ですか?
「パンダス」と読みます。計量経済学の「panel data(パネルデータ)」が名前の由来で、動物のパンダとは関係ありません。
import pandas as pdの「pd」は何の意味ですか?
モジュールに付ける別名です。pandas.read_csv を pd.read_csv と短く書くための慣習で、公式ドキュメントやほとんどのサンプルコードがこの別名を使っています。別の名前でも動きますが、他人が読むコードでは pd に揃えるのが無難です。
PandasとNumPyの違いは何ですか?
NumPyは同じデータ型の要素が並ぶ多次元配列を扱うライブラリで、Pandasはその上に列名・行ラベル・欠損値の扱いを足した表のライブラリです。Pandasの内部ではNumPyが動いており、3.1.0はNumPy 2.0.2以上を必須依存にしています。
Pandasは無料で商用利用できますか?
できます。ライセンスはBSD 3-Clauseで、著作権表示とライセンス文を残せば商用のシステムに組み込めます。
Pandas 3.1にすぐ上げるべきですか?
新規プロジェクトでは、併用ライブラリがPython 3.11以上とNumPy 2に対応していることを確認したうえで、3.1.0を採用候補にします。2.x系の既存コードは、2.3で警告を消してから3.0、さらに3.1へ進めます。上げる前に、連鎖代入・dtype == object による文字列判定・inplace=True の3つをgrepで洗い出してください。NumPy 1.x系に依存する他のライブラリがある環境では、3.0.6に留めて先にNumPy 2への対応を済ませます。