Upgrade to Pro — share decks privately, control downloads, hide ads and more …

NumPy、Pandas、Polarsの内部構造をAIと一緒に探索してみた件について

Avatar for Kazuaki Higa Kazuaki Higa
August 19, 2026
120

 NumPy、Pandas、Polarsの内部構造をAIと一緒に探索してみた件について

PyConJP 2026の登壇資料になります
https://2026.pycon.jp/ja/talks/3C9V3D

Avatar for Kazuaki Higa

Kazuaki Higa

August 19, 2026

Transcript

  1. プロフィール • • • • • • X:@higakazuaki9 比嘉 一晃(ひが

    かずあき) 沖縄県出身 株式会社iimon エンジニア ◦ 不動産DXのツールを提供 https://iimon.co.jp/ 担当業務の技術スタック ◦ vanilla-tsやReact、Django、 AWS、Google Cloud (Firebase)...etc 経験 ◦ Angular、React、Vue.js、Spring Boot、Django、Fast API、Ruby on Rails…etc 性格 ◦ 人懐っこい、面倒くさがり、ねこと ビール好き
  2. 内部構造の探索:NumPy編 配列の簡単な例 pip install numpy import numpy as np arr

    = np.array([[1, 2, 3], [4, 5, 6]]) # [[1, 2, 3], [4, 5, 6]] 2次元の 配列オブジェクト 配列が持つ属性の一例 属性名 概要 出力例 1 2 3 shape 配列の形状 (2, 3) 4 5 6 dtype データ型 int64 size 要素の数 6 T 転置 [[1, 4], [2, 5], [3, 6]] arr.ndim # 次元数 → 2
  3. 内部構造の探索:NumPy編 dtypeについて NumPyがサポートするデータ型 dtypeの適切な指定により、処理のパフォーマンスも向上する データ型の例) ・整数 int8, int16, int32, int64

    ・整数(符号なし) uint8, uint16, uint32, uint64 ・浮動小数点数 float16, float32, float64 ・複素数 complex64, complex128 ・ブール値・文字列 bool, str ・オブジェクト object Pythonのデータ型と比べ、豊富な種類を提供
  4. 内部構造の探索:NumPy編 様々な配列の操作 arr2 = arr.reshape(3, 2) # 形状変更 arr2[0] =

    10 # インデックスによるアクセス arr3 = arr2[1:] # スライス arr4 = np.concatenate((arr2, arr2), axis=1) # 配列の結合 arr5 = arr2.flatten() # 1次元へのフラット化 arr6 = np.unique(arr5) # 重複削除 arr7 = np.sort(arr6, descending=True) # ソート(降順) arr2 arr3 10 10 3 3 4 5 6 5 arr4 4 10 10 10 10 6 3 4 3 4 5 6 5 6 arr5 10 10 3 4 5 arr6 3 4 5 6 10 arr7 10 6 5 4 3 6
  5. 内部構造の探索:NumPy編 配列を操作するための多様な関数 ・形状の変更 reshape, ravel, ndarray.flatten ・配列の結合 concatenate, stack, (h)vstack

    ・軸の入れ替え・転置 transpose(T), swapaxes, moveaxis ・配列の分割 split, (h)vsplit ・次元の変更・拡張 expand_dims, squeeze, broadcast_to ・要素の追加・削除・変更 delete, insert, append, unique ・要素の並び替え・反転 flip, roll, sort
  6. 内部構造の探索:NumPy編 データのファイルIO # バイナリ読み書き np.save('my_array.npy', arr) loaded_array = np.load('my_array.npy') 保存フォーマットの使い分け

    ・バイナリ 高速かつ正確な情報で保存 ・テキスト # バイナリ読み書き(複数配列) 人がみてわかるデータで保存 np.savez('multiple.npz', arr_1=arr, arr_2=arr2) data = np.load('multiple.npz') data['arr_2'] # [[10, 10], [3, 4], [5, 6]] # テキスト(CSVなど)読み書き np.savetxt('data.csv', arr2, delimiter=',', fmt='%.2f') loaded_data = np.loadtxt('data.csv', delimiter=',')
  7. 内部構造の探索:NumPy編 IOの応用:memmapについて ディスク上のファイルを仮想的なメモリ空間にマッピングし、メモリを節約 filename = 'huge_dataset.dat' shape = (10000, 10000)

    dtype = np.float64 必要な部分だけを読み書きする 巨大なデータを扱うときに重宝 mmap_arr = np.memmap(filename, dtype=dtype, mode='w+', shape=shape) mmap_arr[0, :] = np.arange(10000) * 1.5 mmap_arr[5000, 5000] = 12345.6789 mmap_arr.flush() read_only_arr = np.memmap(filename, dtype=dtype, mode='r', shape=shape) read_only_arr[0, :5] # [0. 1.5 3. 4.5 6. ] read_only_arr[5000, 5000] # 12345.6789
  8. 内部構造の探索:NumPy編 数値計算における関数 # 要素同士の加算と行列積 ndarray = np.arange(3).reshape(3, 1) np.add(ndarray, ndarray)

    # 加算 np.matmul(ndarray, ndarray.T) # 行列積 0 0 1 1 2 2 add => 0 0 2 1 4 2 0 1 2 matmul => 0 0 0 0 1 2 0 2 4
  9. 内部構造の探索:NumPy編 いろいろなufunc ・基本的な数学関数 add, subtract, multiply, divide, power, mod, absolute,

    negative, sign, floor, ceil, round ・三角関数・逆三角関数 (arc)sin, (arc)cos, (arc)tan ・指数・対数関数 exp, expm1, log, log10 ・ビット演算 bitwise_and(or, xor), invert, left(right)_shift ・比較演算 greater(_equal), less(_equal), (not_)equal ・浮動小数点・その他の処理 isnan, isinf, isfinite, maximum, minimum
  10. 内部構造の探索:NumPy編 演算や関数の裏で利用されるufunc ndarray = np.arange(3).reshape(3, 1) ndarray + ndarray #

    = np.add(ndarray, ndarray) = [[0], [2], [4]] 演算の裏で実はufuncが動作 利用例) ・基本的な演算 + → add - → subtract * → multiply / → divide // → floor_divide ** → power % → mod ・比較演算 == → equal != → not_equal < → less <= → less_equal > → greater >= → greater_equal % → mod ・集計関数 sum → add.reduce prod → multiply.reduce max → maximum.reduce min → minimum.reduce all → logical_and.reduce any → logical_or.reduce
  11. 内部構造の探索:NumPy編 高度な数値計算 NumPyに梱包されている高度な数値計算のためのライブラリ Fortranベースで構成されている処理も多数あり Fortran(Formula Translation): 数値計算・科学技術計算に特化したプログラミング言語 BLAS (Basic Linear

    Algebra Subprograms): 線形代数の基礎的な演算を提供するライブラリ ベクトル同士の計算や行列とベクトルの演算などで活躍 LAPACK (Linear Algebra PACKage): BLASをベースに構築された高度な線形代数計算のためのライブラリ FFTPACK(Fast Fourier Transform PACKage): 高速フーリエ変換(FFT)とその逆変換などを計算するためのライブラリ
  12. 内部構造の探索:NumPy編 高度な数値計算の関数たち ・行列積・内積に関する関数 dot, matmul, inner, vdot ・numpy.linalg モジュール (線形代数ライブラリ)

    inv, solve, det, eig(h), qr, svd 【補足】F2PYについて Fortran to Python interface generatorの略称 Fortranで書かれた関数や サブルーチンをPythonから 呼び出すことを可能にする ・numpy.fft モジュール (高速フーリエ変換) fft, ifft 高度な数値の計算機能を提供 様々なツールの計算基盤として利用される
  13. 内部構造の探索:NumPy編 簡単な計算速度の比較(vs Pythonリスト) # 100万行の加算を計測 size = 10_000_000 py_list =

    list(range(size)) np_arr = np.arange(size) py_result = [x + 1 for x in py_list] # →0.66 s np_result = np_arr + 1 # →0.02 s NumPy配列の圧勝!
  14. 内部構造の探索:NumPy編 配列が持つ制限について 配列が持つ3つの制限 ・配列のすべての要素は同じデータ型である必要がある ・一度作成された配列のサイズは変更できない(イミュータブル) ・形状は長方形で統一されていなければならない # 制限にかかる例 ndarray =

    np.array([ 1 , 1 , [ 1 ]]) # データ型が異なるため、エラー ndarray.append( 4 ) # 要素の個数は変更できない(そもそもappendがない)ため、エラー ndarray = np.array([[ 1 ], [ 1 ], [ 1 , 2 ]]) # 形状が統一されていないため、エラー # object を指定するとどの値でも作成可能だが非推奨 ndarray = np.array([[ 1 ], [ 1 ], [ 1 , 2 ]], dtype = object ) # array([list([1]), list([1]), list([1, 2])], dtype=object)
  15. 内部構造の探索:NumPy編 配列が持つ制限について 配列が持つ3つの制限 ・配列のすべての要素は同じデータ型である必要がある ・一度作成された配列のサイズは変更できない(イミュータブル) ・形状は長方形で統一されていなければならない これらの制約は # 制限にかかる例 NumPyのメモリレイアウトに由来

    ndarray = np.array([ 1 , 1 , [ 1 ]]) # データ型が異なるため、エラー ndarray.append( 4 ) # 要素の個数は変更できない(そもそもappendがない)ため、エラー ndarray = np.array([[ 1 ], [ 1 ], [ 1 , 2 ]]) # 形状が統一されていないため、エラー # データ型が同じであれば良いので、object を指定することも可能だが非推奨 ndarray = np.array([[ 1 ], [ 1 ], [ 1 , 2 ]], dtype = object ) # array([list([1]), list([1]), list([1, 2])], dtype=object)
  16. 内部構造の探索:NumPy編 配列のメモリレイアウト NumPyの配列は、全要素を連続した一列のメモリブロック格納 Pythonのリストにおけるメモリレイアウト P1 p2 P3 P4 P5 P6

    ・データの参照ポインタを格納 ・バラバラなデータ型でも柔軟に管理可能 ・データの実体は別の場所にあるため 探し出す手間がオーバーヘッドとなる NumPyの配列におけるメモリレイアウト 1 2 3 4 5 6 ・データの実体を連続して一列に格納 ・メモリ効率を最大化し高速処理を実現 ・実体を一列に連続して格納するため それに伴い制限が必要となる C言語やFortranと親和性があり、キャッシュヒット率が大幅に向上 このメモリレイアウトに加え、shapeとstridesを使って多次元を表現
  17. 内部構造の探索:NumPy編 shapeとstrides 配列のインスタンス化時にデータの並び順を指定することも可能 import numpy as np ndarray_c = np.array([[

    1 , 2 ], [ 3 , 4 ], [ 5 , 6 ]], order = 'C' ) ndarray_f = np.array([[ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 ]], order = 'F' ) ndarray_c.shape # (3, 2) ndarray_c.strides # (16, 8) ndarray_f.shape # (3, 2) ndarray_f.strides # (8, 24) C言語は行優先 Fortranは列優先 優先するデータの並び順に応じて stridesが変化 これらの情報をもとに、C言語やFortranとスムーズな連携を実現
  18. 内部構造の探索:NumPy編 Viewについて 既存の配列から新規作成された配列 データをコピーするのではなく、元の配列を参照する形で作成される ndarray = np.array([1, 2, 3, 4,

    5]) ndarray_view = ndarray[::2] # [1, 3, 5] 【余談】スライスの挙動差 ・Pythonのリスト →コピーを作成 ndarray_view[0] = 0 # baseで参照元の配列を確認 ・NumPy配列 ndarray_view.base # array([[0, 2, 3, 4, 5]]) →Viewを作成 ndarray_view.strides # (16,) ← 2段飛ばしのstrides (別途copy関数あり) NumPyの配列生成は基本的にViewを返す
  19. 内部構造の探索:NumPy編 Viewについて 既存の配列から新規作成された配列 データをコピーするのではなく、元の配列を参照する形で作成される ndarray = np.array([1, 2, 3, 4,

    5]) ndarray_view = ndarray[::2] # [1, 3, 5] 【余談】スライスの挙動差 ・Pythonのリスト 無駄なコピーの発生を抑え、省メモリで高速な処理に繋げている →コピーを作成 ndarray_view[0] = 0 # baseで参照元の配列を確認 ・NumPy配列 ndarray_view.base # array([[0, 2, 3, 4, 5]]) →Viewを作成 ndarray_view.strides # (16,) ← 2段飛ばしのstrides (別途copy関数あり) スライス以外の操作でも、コピーの必要がない場合はViewを返す
  20. 内部構造の探索:NumPy編 ブロードキャストの裏側 ブロードキャスト時もViewやshape、stridesを活用 余分なコピーは発生しない v = np.array([1, 2, 3]) #

    array([1, 2, 3]) # ブロードキャスト bc = np.broadcast_to(v, (3, 3)) # array([[1, 2, 3], [1, 2, 3], [1, 2, 3]]) # ブロードキャスト後の形状や参照 bc.shape # (3, 3) bc.strides # (0, 8) bc.base # array([1, 2, 3]) stridesを0で表現し 大きい方の形状に 沿って演算する
  21. 内部構造の探索:NumPy編 C-APIの種類(用途・役割) C-APIの種類 用途・役割 Array API 配列自体の作成、形状やストライドの取得、メモリ管理 Data type API

    データ型(dtype)の判定や生成 Array iterator API 多次元配列におけるループの高速かつ安全なイテレーション ufunc API ufuncの拡張(カスタマイズ)や定義 gufunc API 汎用ufunc(行列の掛け算など)の拡張(カスタマイズ)や定義 NpyString API NumPyの文字列データをC言語側で安全かつ効率的に操作 Datetime API NumPyの時系列データをC言語側の構造にパース
  22. 内部構造の探索:NumPy編 C-APIの種類(主なデータ構造の例) C-APIの種類 主要なデータ構造の例 Array API PyArrayObject、PyArray_Chunk、PyArray_Conn Data type API

    PyArray_Descr Array iterator API NpyIter、NpyIter_IterNextFunc ufunc API PyUFuncObject、PyUFuncGenericFunction gufunc API PyUFuncObject NpyString API NpyString_Type Datetime API PyArray_DatetimeMetaData
  23. 内部構造の探索:NumPy編 C言語のパワーを活かしたカスタムな処理の実装例 nditer:NumPyが提供している配列イテレータ用のオブジェクト 内部でNpyIterを用いた高速な処理を利用 # 例:1000万個の配列同士の加算処理の比較(VS Pythonのリスト) [x + y

    for x, y in zip(list_a, list_b)] # 0.85 s with np.nditer([array_a, array_b, None], flags=['external_loop', 'buffered'], op_flags=[['readonly'], ['readonly'], ['writeonly']]) as it: for x, y, o in it: print('Hi!') # カスタム処理(ループ挨拶) ・引数で様々な設定が可能 ・C言語による高速処理 o[...] = x + y # 0.06 sですべてのループ完了 ・柔軟なカスタマイズ
  24. 内部構造の探索:NumPy編 SIMDの活用 SIMD: 単一命令・複数データ(Single Instruction, Multiple Data)の略称 一つの命令で複数のデータを並列処理することができるCPUの機能 NumPyのメモリレイアウトとも相性が良く、メモリアクセスで 待たされる時間も短いためSIMDの機能をフル活用できる

    Universal Intrinsics: CPUの種類によって命令セットが異なるため、共通のインタフェース としてNumPyが提供するマクロのセット コンパイル時の命令の有効化や実行時のディスパッチなどもサポート ※ 最適化のオフなどもユーザー側で柔軟に設定することも可能
  25. 内部構造の探索:Pandas編 SeriesとDataFrameの具体例 pip install pandas import pandas as pd import

    numpy as np df = pd.DataFrame({ NumPy配列のデータも利用可 'ID': pd.Series(np.array([100, 200, 300]), index=['r1', 'r2', 'r3']), 'Category': ['X', 'Y', 'Z'] }) df df["Category"] DataFrameの各列は Seriesで構成
  26. 内部構造の探索:Pandas編 DataFrameの基本操作(フィルタ、マージ、欠損値処理) df2 = df[df["Category"]=="X"] # フィルタ df3 = pd.merge(df,

    pd.DataFrame({ 'ID': pd.Series(np.array([100, 200, 300]), index=['r1', 'r2', 'r3']), 'Category': ['A', 'B', None] }), on="ID", how="inner") # マージ 多様で便利なデータ操作 df4 = df3.fillna("!!!") # 欠損値処理 df2 df3 df4
  27. 内部構造の探索:Pandas編 Pandasの基本操作(データ変形(集計・グループ化・構造変更)) df["ID_2x"] = df["ID"] * 2 df["ID_3x"] = df["ID"]

    * 3 df["ID_total"] = df["ID_2x"] + df["ID_3x"] 列の計算や構造変更も スムーズに実行 df5 = df.groupby("Category")[["ID_2x", "ID_3x", "ID_total"]].sum() df df5
  28. 内部構造の探索:Pandas編 Pandasの基本操作(ファイルIO) csv_filename = "df_pd.csv" # CSVへの読み書き df.to_csv(csv_filename) pd.read_csv(csv_filename) 表データの保存形式一覧

    幅広い保存形式に対応 必要最低限のデータ取得 など、オプションも豊富 read_csvのオプション例 usecols:取得列の指定 dtype:データ型の指定 chunksize:分割のサイズ メソッド名 保存形式 read_csv / to_csv CSV、テキスト read_excel / to_excel エクセル read_parquet / to_parquet Parquet read_ json / to_ json JSON read_sql / to_sql SQL read_pickle / to_pickle Pickle
  29. 内部構造の探索:Pandas編 Pandasの基本操作(グラフ描画) df_loaded = pd.read_csv(csv_filename) df_loaded.plot( x="month", y="revenue", kind="bar", title="月別売上推移",color="steelblue",

    grid=True, ) import matplotlib.pyplot as plt # Matplotlibによる描画 plt.show() Matplotlib:Pythonのあらゆるデータ を柔軟に可視化することに特化した ライブラリ
  30. 内部構造の探索:Pandas編 DataFrameが提供している関数 ・基本属性・構造確認 ・変形・操作・結合 shape, ndim, size, dtypes, apply, map,

    applymap, columns, index, values sort_values, sort_index, ・インデックス・選択 pd.merge, pd.concat loc, iloc, head, tail, query ・グループ化・集計 ・集計・統計演算 groupby, pivot_table, melt sum, mean, std, min, max, describe ・入出力・変換 ・欠損値・データ整形 to_numpy, to_dict, to_csv isna, notna, fillna, dropna to_parquet, to_ json ・特殊アクセサ (拡張)、可視化 複雑で大量の現実データを柔軟に効率 str, dt, cat, plot よく捌けるように多くの機能を提供
  31. 内部構造の探索:Pandas編 データのアライメントと演算について アライメント: 異なる形式のデータに対して、インデックスやカラムを自動で揃える機能 pd.DataFrame(np.ones((3, 3)),index=["r1", "r2", "r3"],columns=["A", "B", "C"])

    pd.Series([10, 20], index=["B", "X"]) A B C B X r1 N 1 N 10 N r2 N 1 N 10 r3 N 1 N 10 ※ NはNanの略 + A C X r1 N 11 N N N r2 N 11 N N N r3 N 11 N N = B 形式の異なるデータ でも手動の調整は不要 ブロードキャストも サポート ※ アライメント優先
  32. 内部構造の探索:Pandas編 Copy-on-Write (CoW)について スライスや列抽出、フィルタリングなどで作成されたオブジェクトは df2 論理的なコピーとして扱われる df df = pd.DataFrame(

    {"A": [0,1],"B": [2,3]}, index=["r1", "r2"] ) df2 = df[:] df3 = df[["A"]] df4 = df.loc[df["B"] > 2] df2.loc["r1","A"]=2 値を変更しても元の df3.loc["r1","A"]=3 値に影響を受けない df4.loc["r2","A"]=4 df3 df4
  33. 内部構造の探索:Pandas編 Pandasがサポートするデータ型たち 各データ型例) ・NumPyのデータ型 int64, float64, boolなど ・Pandas独自の拡張型 Nullable拡張, Categorical

    時系列・空間型, Sparse Int64Dtype, CategoricalDtype PeriodDtype, IntervalDtype, SparseDtypeなど ・PyArrow系 数値型, 文字列・真偽値型 日時・時間型, 複雑な構造・高度な型 int64, float64, decimal128 string, bool_ timestamp, date64, time64, duration dictionary, list_, map_など NumPyのデータ型をベースに、多種多様なデータ型をサポート
  34. 内部構造の探索:Pandas編 Pandasの欠損値の取り扱いについて Pandasはデータの中に欠損値が混ざるとアップキャストが発生 s_int = pd.Series([1, 2, None]) s_bool =

    pd.Series([True, False, None]) s_int.dtype # →float64 データの中に欠損値が混ざると アップキャストが発生 s_bool.dtype # object アップキャストによる弊害 ・意図しない変換による結合時のエラー ・無駄なメモリ消費量の増大 ・一意性などの判定ミス ・機械学習モデルへの誤ったバイアスの持ち込み など
  35. 内部構造の探索:Pandas編 Nullable拡張型の役割 Nullable拡張型で不要なアップキャストを防ぐ s_int = pd.Series([1, 2, None], dtype="Int64") s_bool

    = pd.Series([True, False, None], dtype="bool") s_int.dtype # →Int64 欠損値を含んでいても 指定した型を維持 s_bool.dtype # bool 補足:マスクの情報 Pandasは本体のデータとは別にマスク情報 を保持し、この情報をもとに欠損値を捌く 演算や集計、IOなど様々な場面でも利用 print(s_int.mask) # <bound method NDFrame.mask of #0 1 #1 2 # 2 <NA> # dtype: Int64>
  36. 内部構造の探索:Pandas編 カテゴリ型について データのカテゴライズにより、効率的で高速な処理を実現する型 種類が限られている文字列などで特に有効 カテゴリ型の内部構造 categories:ユニークな値のリスト codes:各行の値がcategoriesの何番目に該当するかを示す整数配列 文字列型 ["A", "A",

    "B", "B", "C", "C"] カテゴリ型 categories: ["A", "B", "C"] codes: [0, 0, 1, 1, 2, 2] 辞書エンコーディング:繰り返す値を一意の辞書としてまとめ、 実際のデータは小さな整数で表現することでデータを圧縮する技術
  37. 内部構造の探索:Pandas編 カテゴリ型による改善の検証 # 100万行のシリーズにおける比較(vs 文字列) data = np.random.choice(["ワイン", "ハイボール", "ビール"],

    size=1_000_000) s_obj = pd.Series(data) s_cat = pd.Series(data).astype("category") s_cat = s_cat.cat.set_categories( ["ワイン", "ハイボール", "ビール"], ordered=True ) s_obj.memory_usage() # 18.12 MB s_cat.memory_usage() # 0.95 MB s_obj.groupby(s_obj).count() # 0.03 s s_cat.groupby(s_cat).count() # 0.01 s s_cat.max() # ビール メモリと処理速度の改善 orderを設定することで データ同士の比較も可能
  38. 内部構造の探索:Pandas編 PyArrowとPandasのデータ連携(Pandas → PyArrow) import pyarrow.parquet as pq import pyarrow

    as pa df_pd = pd.DataFrame({ "name": ["Aさん", "Bさん"], "age": [28, 34], "pref": ["広島", "東京"] }) # DataFrame → Tableへの変換とParquetへの保存 table_from_pd = pa.Table.from_pandas(df_pd) pq.write_table(table_from_pd, "data.parquet") Parquet:大規模なデータの取り扱いに特化した ファイル保存形式(ストレージフォーマット) Arrow形式のデータを圧縮してコンパクトに保存
  39. 内部構造の探索:Pandas編 PyArrowとPandasのデータ連携(PyArrow → Pandas) table_loaded = pq.read_table("data.parquet") new_column = pa.array([200,

    100]).sort() new_table = table_loaded.append_column("score", new_column) new_df = new_table.to_pandas() # テーブル → DataFrameへの変換 # DataFrame ↔ ParquetのIO new_df.to_parquet("df_pd_data.parquet") df_loaded = pd.read_parquet("df_pd_data.parquet")
  40. 内部構造の探索:Pandas編 PyArrow&ParquetとPandas&CSVのIO速度比較 N = 1_000_000 # 100万行データのDataFrame df = pd.DataFrame({

    "id": range(N), "category": [f"cat_{i % 10}" for i in range(N)], "value": [float(i) * 1.5 for i in range(N)], }) table = pa.Table.from_pandas(df) df.to_csv("test.csv", index=False) # 1.754s pq.write_table(table, "test.parquet") # 0.115s df_csv = pd.read_csv("test.csv") # 0.269s table_par = pq.read_table("test.parquet") # 0.027s 読み書きともに PyArrow&Parquetが 圧勝!
  41. 内部構造の探索:Pandas編 Apache Arrowを採用することによるメリット ・省メモリかつ高速な処理の実現 キャッシュヒット率の向上やゼロコピー、 効率的な欠損値の管理、SIMDの活用、 ネスト構造など複雑なデータへも対応 Pandas Polars シリアライズや

    コピー・変換などの オーバーヘッド Parquet ・「言語・システム間の壁」の払拭 プログラミング言語やシステムの広いサポート メモリの標準規格による処理のコストカット 不要なシリアライズ・パース処理の抑制 ・ストレージ、ネットワーク通信の効率化 Apache ParquetやApache Featherなど 連携コストの大幅削減 Pandas Arrow Memory Parquet Polars
  42. 内部構造の探索:Pandas編 Apache Arrowの広がり 様々な言語やそれらのライブラリ、エコシステムなどでApache Arrowの サポートが広がってきている ・各プログラミング言語 .NET、C++、C GLib、Go、Java、JavaScript、Julia MATLAB、Python、R、Ruby、Rust、Swift...

    ・ライブラリやエコシステム NumPy、Pandas、Polars、Hugging Face、PyTorch、TensorFlow DuckDB、Snowflake、BigQuery、Databricks Apache Spark、Apache Flink、Apache Parquet、Apache Iceberg ...
  43. 内部構造の探索:Pandas編 Apache Arrowの広がり 様々な言語やそれらのライブラリ、エコシステムなどでApache Arrowの サポートが広がってきている ・各プログラミング言語 .NET、C++、C 大規模データを扱う様々なシステムにおいて GLib、Go、Java、JavaScript、Julia

    急速にサポートが広がっている MATLAB、Python、R、Ruby、Rust、Swift... ・ライブラリやエコシステム NumPy、Pandas、Polars、Hugging Face、PyTorch、TensorFlow DuckDB、Snowflake、BigQuery、Databricks Apache Spark、Apache Flink、Apache Parquet、Apache Iceberg ...
  44. 内部構造の探索:Polars編 Polarsの基本操作(DataFrame) pip install polars import numpy as np import

    polars as pl fruits = ["apple", "banana", "cherry", "date"] prices = pl.Series("price", [120, 80, 150, 200]) stocks = np.random.randint(10, 50, size=4) category_ids = [1, 2, 1, 3] df = pl.DataFrame({ "fruit": fruits, "category_id": category_ids, "price": prices, "stock": stocks Pandasにインスパイアされた }) DataFrame
  45. 内部構造の探索:Polars編 Polarsの基本操作(ファイルIO) csv_filename = "df_pd.csv" # CSVへの読み書き df.to_csv(csv_filename) pl.read_csv(csv_filename) 表データの保存形式一覧

    幅広い保存形式に対応 Apache Arrow形式の データのサポートや スキャンによる高速な データ連携が可能 (後述) メソッド名 保存形式 read_csv / write_csv CSV、テキスト read_excel / write_excel エクセル read_parquet / write_parquet Parquet read_ json / write_ json JSON read_ipc / write_ipc (プロセス間通信) Arrowの ネイティブ形式
  46. 内部構造の探索:Polars編 500万行のデータにおける処理速度の比較 vs Pandas # Pandas(1.36 s) pd_df = pd.read_csv(csv_path)

    pd_df["total"] = pd_df["price"] * pd_df["quantity"] pd_filtered = pd_df[pd_df["category"].isin(["A", "B"])] pd_filtered.groupby("category").agg(sum_total=("total", "sum")) # Polars(0.15 s) Polarsだと9倍近く速い!!! pl_df = pl.read_csv(csv_path) pl_df.with_columns( (pl.col("price") * pl.col("quantity")) .alias("total")).filter(pl.col("category") .is_in(["A", "B"])).group_by("category").agg(pl.col("total") .sum().alias("sum_total")).sort("category")
  47. 内部構造の探索:Polars編 PandasとPolarsの比較(主な相違点) Pandas Polars インデックスの有無 行ラベル 自動採番インデックス なし すべてフラットな列データ メモリ構造とストレージ

    NumPyベース Apache Arrowベース 評価モデル(遅延評価) 即時評価 遅延評価 並列処理の仕組み 基本はシングルスレッド マルチスレッド 記述の思想と一貫性 多様な記法 「式(Expressions)とコン テキスト」 で一貫した記法
  48. 内部構造の探索:Polars編 Polarsのデータ型 Polarsのデータ型はApache Arrowに準拠 データ型の例) ・整数型 Int8, Int16, Int32, Int64

    UInt8, UInt16, UInt32, UInt64 ・浮動小数点型 Float32, Float64 ・ブール型 Boolean ・文字列・バイナリ型 String, Binary ・日時・時間型 Date, Datetime, Time, Duration ・複合・入れ子型 List, Array, Struct ・特殊型 Categorical, Enum, Null
  49. 内部構造の探索:Polars編 Pythonバインディング PolarsにおけるPythonバインディングのイメージ Python層 Rust層 Python層 Pythonの GILを解放 Pythonコード LazyFrameの

    クエリなど マルチスレッドで並列実行 ・SIMD命令 ・ベクトル化バッチ処理など 抽象的なクエリ プランを作成 (DSL / 中間表現) 結果を Apache Arrow の メモリバッファに格納 Arrowの形で受け取り スムーズに処理
  50. 内部構造の探索:Polars編 ファイルのスキャン機能 Polarsはファイルのスキャン機能も提供 ファイルのメタデータを先読みしてアクセスを最適化する ・min、maxによるデータチェック ・Nullの数と位置の把握し、Nullチェックの効率化 ・複数ファイルのスキャンおよびファイルの開閉是非の判定 # 100万行データ ×

    3ファイルでフィルタ処理の速度比較(VS read) df_all = pl.read_parquet("sample_data/data_*.parquet") df_eager_result = df_all.filter(pl.col("year") == 2026) # 0.0484 s df_lazy_result = ( scan_parquetだと pl.scan_parquet("sample_data/data_*.parquet") .filter(pl.col("year") == 2026).collect() 約2.8倍速!!! ) # 0.0175 s
  51. 内部構造の探索:Polars編 遅延評価とクエリ最適化(オプティマイザ)について Polarsの遅延評価はコードを即時実行せず、クエリプランを構築 →オプティマイザが実行前にプランを解析し効率的な順序で実行 df = pl.DataFrame({"id": range(100), "category": ["A",

    "B"] * 50, "price": [100, 1500, 200, 2500] * 25, "heavy_description": ["Very long text data..." * 10] * 100 }).write_parquet("sample.parquet") lf = pl.scan_parquet("sample.parquet") LazyFrameの例 この段階ではまだ メモリ上にデータを 展開しない
  52. 内部構造の探索:Polars編 クエリ最適化の流れをみてみよう # 冗長なクエリで対象のデータを取得する処理 query = ( df.select( ["id", "price",

    "heavy_description"] ).filter(pl.col("price") > 1000) .select(["id", "price"]) ) クエリ実行前に query.explain() query.show_graph() プランを確認できる res = query.collect() # クエリの実行
  53. 内部構造の探索:Polars編 最適化の様々なオプション 最適化のオプション 一例) オプション名 オプションの内容 predicate_pushdown スキャン時にフィルタを適用してメモリを節約する projection_pushdown 必要な列データだけ読み込んでメモリを節約する

    comm_subexpr_elim 同じ処理をキャッシュして再利用する simplify_expression 冗長な式をシンプルで高速な処理に書き換える slice_pushdown スキャン時にスライスを適用してメモリを節約する LazyFrameでcollectする際はこれらがデフォルトONで実行される
  54. 内部構造の探索:Polars編 CPUコア数による処理速度の違い os.environ["POLARS_MAX_THREADS"] = "1" 3000万行のデータ ... で複雑な処理を実行 result =

    ( df コア数1:2.76 s .group_by("category") .agg([ pl.col("val1").mean().alias("val1_mean"), pl.col("val2").max().alias("val2_max"), pl.col("val1").std().alias("val1_std"), pl.col("val2").quantile(0.5).alias("val2_median"), ]) ) result.collect() if isinstance(result, pl.LazyFrame) else result
  55. 内部構造の探索:Polars編 CPUコア数による処理速度の違い os.environ["POLARS_MAX_THREADS"] = "8" 3000万行のデータ ... で複雑な処理を実行 result =

    ( df コア数1:2.76 s .group_by("category") コア数8:0.79 s .agg([ コア数を増やすことで pl.col("val1").mean().alias("val1_mean"), 処理速度があがる pl.col("val2").max().alias("val2_max"), pl.col("val1").std().alias("val1_std"), pl.col("val2").quantile(0.5).alias("val2_median"), ]) ) result.collect() if isinstance(result, pl.LazyFrame) else result
  56. Appendix 相棒の回答 実は、NumPyの内部実装に関する「PythonのListを継承しているの か?」という疑問は、内部の仕組みを知る上で最も面白くて、かつ多く の人が驚くポイントです。 結論からお伝えすると、NumPyの ndarray は、Pythonの list を一切継

    承していません。 全く別の、C言語ベースの独立したデータ構造として 一から作られています。 では、なぜPythonの len() が使えたり、リストのように振る舞ったりで きるのでしょうか? その核心にあるNumPyの根本実装の秘密を解説しま す。
  57. Appendix 相棒の回答(続) 1. なぜ len() などのPythonの機能が使えるのか? NumPyの配列(ndarray)がPythonのリストを継承していないにもかか わらず、Pythonの組み込み関数や作法と仲良くできるのは、Pythonが 持つ 「C拡張(C-Extension)」

    と 「プロトコル(作法)」 という仕組 みのおかげです。 Pythonは、裏側(CPython)がC言語で動いています。そのため、C言 語で作った独自のオブジェクトに対して「Python風の振る舞いをするた めの専用スロット(API)」を登録することができます。
  58. Appendix 相棒の回答(続) 生データへのポインタ(Data Buffer) その構造体が、綺麗に整列した1次元のメモリ領域(数値の生データ)を 指し示しています。 私たちがPythonで import numpy as

    np と書き、np.array([1, 2, 3]) を 実行した瞬間、Pythonは裏側でC言語のプログラムを呼び出し、OSに 「メモリーをこれだけくれ!」と直接要求して、このC言語の構造体をメ モリ上にスパッと構築しています。
  59. Appendix 相棒の回答(続) 3. ブロードキャストや演算(+ や *)の正体 「ブロードキャストや足し算(arr1 + arr2)などもすべてNumPyで実装 されている」というご推察の通りです。

    Pythonの通常のリストで [1, 2] + [3, 4] をやると、リストの結合([1, 2, 3, 4])になってしまいます。しかし、NumPyの ndarray 同士の足し 算が「要素ごとの足し算([4, 6])」になるのは、Pythonの通常の演算 機能ではなく、NumPyのC言語コード側で「算術演算子の意味を完全に 書き換えている(オーバーライドしている)」からです。 裏側では、C言語や最適化されたCのループ(SIMD命令などを使った超 高速なCPUの並列計算)が走っており、Pythonの「遅い for ループ」を 一切使わずに、一瞬で計算を終わらせて結果を返しています。
  60. Appendix 相棒の回答(続) まとめ Pythonの list を継承している? ❌ していない。まったくの別物。 正体は何か? ⭕

    C言語で書かれた、メモリ管理と高速計算のための超強力なネイティ ブ・オブジェクト。 なぜPythonらしく動くのか? ⭕ PythonのC API(共通の作法)を完璧に実装しているから。 「見た目はPythonの顔をしていながら、中身はゴリゴリのC言語で最適 化された怪物マシン」──それがNumPyの根本の姿です!