別名・略称:(なし)
データ圧縮(Data Compression):ストレージ・通信量を減らす変換
read_csv() や read_parquet() を使う時、 知らないうちに圧縮が動いています。| 形式 | 圧縮率 | 速度 | 用途 |
|---|---|---|---|
| gzip | 高 | 中 | 汎用、 CSV/JSON |
| bzip2 | 非常に高 | 遅 | アーカイブ |
| LZ4 | 中 | 非常に速 | リアルタイム処理 |
| Snappy | 中 | 速 | Parquet標準 |
| Zstd | 高 | 速 | 汎用、 推奨 |
SSDSE-B-2026.csv(約 100 KB)を各形式で保存:
| 形式 | サイズ | 圧縮率 |
|---|---|---|
| CSV(無圧縮) | 100 KB | 1.00 |
| CSV.gz | 25 KB | 0.25 |
| Parquet+Snappy | 18 KB | 0.18 |
| Parquet+Zstd | 12 KB | 0.12 |
SSDSE-B-2026(47 都道府県・2023 年データ)を題材にした最小コード:
1 2 3 4 5 6 7 8 9 10 11 12 | import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) # gzip 圧縮で保存 df.to_csv('data/processed/ssdse.csv.gz', index=False, compression='gzip') # Parquet(Snappy 圧縮、 デフォルト) df.to_parquet('data/processed/ssdse.parquet', index=False) # Parquet + Zstd(より高圧縮) df.to_parquet('data/processed/ssdse_zstd.parquet', compression='zstd') |
圧縮を一言で言えば「パターンを見つけて短く書く」。 「AAAAA」と書く代わりに「A×5」と書けば短くなる、 という発想です。
日本語にも例えれば「あいうえおあいうえおあいうえお」を「[あいうえお]×3」と表現できる。 アルゴリズムごとに「どんなパターンを探すか」が異なります:
データ圧縮は データレイク 設計、 分散処理、 ネットワーク転送、 メモリ効率の全てで効いてくる横断的技術。 「CPU を使ってでも、 ディスク IO / ネットワークを減らす」のが現代的トレードオフ。
分析パイプライン上では、 収集 直後の Bronze 層から最初に適用し、 Silver/Gold まで一貫させます。 列指向+圧縮で 10〜30 倍縮むのが普通で、 ストレージ費・クエリ費の双方を削減します。
圧縮レベルを上げると 圧縮率は向上するが、 CPU 時間が増加します。 全体スループットは IO ボトルネックの解消量とのバランス:
$$ T_{\text{総}} = T_{\text{圧縮}} + T_{\text{IO}} + T_{\text{解凍}} $$
$T_{\text{IO}}$ が支配的(ネットワーク・HDD 書き込み)なら圧縮で総時間短縮。 メモリ内処理が中心なら圧縮はオーバーヘッドになる場合も。
SSD:3GB/s → snappy/lz4 で十分(CPU が追いつく)
HDD/Network:100MB/s 程度 → zstd レベル 3〜6 で IO 短縮効果が CPU コストを上回る
データ圧縮はデータ基盤の費用・性能を左右する鍵。 「列指向+現代的可逆圧縮」が分析用途の標準。 一次的なシナリオごとに、 圧縮速度・解凍速度・圧縮率のトレードオフを意識すれば、 大半の問題は解決します。
ネットワーク経由のデータ転送でも圧縮は必須。 主要なケース:
| プロトコル | 圧縮方式 | 用途 |
|---|---|---|
| HTTP | gzip, brotli, zstd | Web ページ転送 |
| gRPC | gzip, snappy | マイクロサービス通信 |
| Kafka | snappy, lz4, zstd | ストリーミング |
| MQTT | アプリ層で gzip | IoT センサー |
| SSH | zlib | リモート操作 |
SSDSE-B 程度の表形式データを各方式で圧縮した場合の代表的な数値(実測値は環境依存):
| 形式 | サイズ(CSV比) | 読み速度 | 書き速度 |
|---|---|---|---|
| CSV(基準) | 100% | 1.0× | 1.0× |
| CSV.gz | 25% | 0.8× | 0.3× |
| Parquet (snappy) | 15% | 5× | 3× |
| Parquet (zstd) | 10% | 4× | 2× |
| ORC (zstd) | 9% | 4× | 2× |
| Feather/Arrow | 35% | 10× | 10× |
圧縮は 可逆(lossless) と 不可逆(lossy) に大別されます。 データサイエンスでは可逆が基本:
| アルゴリズム | タイプ | 圧縮率 | 速度 | 向く用途 |
|---|---|---|---|---|
| gzip (DEFLATE) | 可逆 | 中 | 中 | 汎用、Web |
| bzip2 (BWT) | 可逆 | 高 | 遅 | アーカイブ |
| xz (LZMA2) | 可逆 | 極高 | 遅 | 長期アーカイブ |
| zstd (Zstandard) | 可逆 | 高 | 速 | 現代の汎用最強 |
| snappy | 可逆 | 低 | 極速 | Parquet, Spark |
| lz4 | 可逆 | 低 | 極速 | リアルタイム |
| brotli | 可逆 | 高 | 中 | Web(HTTP) |
| JPEG | 不可逆 | 高(10〜100倍) | 速 | 写真 |
| H.264/H.265 | 不可逆 | 極高 | 中 | 動画 |
2025 年現在、 大量データのデフォルトは Parquet + zstd または Parquet + snappy。 zstd は近年急速にシェア拡大。
分析データは 列指向(columnar) 形式で保存すると圧縮率が劇的に向上します。 同じ列に並ぶ値は型と分布が似ているため、 アルゴリズムが効きやすくなるからです。
$$ \text{圧縮率} = \frac{\text{元サイズ}}{\text{圧縮後サイズ}} $$
| 技法 | 原理 | 効果 |
|---|---|---|
| RLE (Run-Length) | 同値の連続を回数で表現 | 繰り返し多い列で 100 倍 |
| 辞書符号化 | 値を ID に置換 | カテゴリ列で 5〜20 倍 |
| ビットパッキング | 最小ビット幅に圧縮 | 整数列で 2〜4 倍 |
| 差分符号化 (Delta) | 隣接値との差を保存 | 時系列で大幅縮小 |
| FOR (Frame of Reference) | 基準値からのオフセット | 範囲狭い数値で効く |
| 汎用圧縮(zstd等)を後段 | エンコード済み列をさらに圧縮 | 最終的に 10〜30 倍 |
(1) 各圧縮方式の比較:
1 2 3 4 5 6 7 8 9 10 | import pandas as pd import os, time df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) for comp in ['snappy', 'gzip', 'brotli', 'zstd', 'lz4']: t = time.time() df.to_parquet(f'/tmp/test_{comp}.parquet', compression=comp) sz = os.path.getsize(f'/tmp/test_{comp}.parquet') print(f'{comp}: {sz/1024:.1f} KB, {time.time()-t:.2f}s') |
(2) DataFrame の dtype 最適化で圧縮率を上げる:
1 2 3 4 5 6 7 8 9 10 11 12 | import numpy as np # int64 を int32 へ df['人口総数'] = df['人口総数'].astype('int32') # object → category df['都道府県'] = df['都道府県'].astype('category') # float64 を float32 へ(精度が許せば) df['高齢化率'] = df['高齢化率'].astype('float32') print('メモリ使用量:', df.memory_usage(deep=True).sum() / 1024, 'KB') |
(3) ストリーミング圧縮(巨大ファイルでメモリ節約):
1 2 3 4 5 6 7 8 9 | import gzip, shutil # 大きな CSV を gzip 圧縮 with open('large.csv', 'rb') as f_in: with gzip.open('large.csv.gz', 'wb', compresslevel=6) as f_out: shutil.copyfileobj(f_in, f_out) # pandas で直接読める df = pd.read_csv('large.csv.gz', encoding='utf-8') |
どんなに優れた可逆圧縮でも、 Shannon エントロピー以下にはできません。 確率分布 $p(x)$ を持つ情報源の理論的下限:
$$ H(X) = -\sum_i p_i \log_2 p_i \quad [\text{bits/symbol}] $$
例:8 ビット文字でも、 ASCII 英文の実効エントロピーは約 4.7 bit/char。 つまり理論上 1.7 倍圧縮できる。 実際の gzip でも 2〜3 倍程度に収まります。
乱数や既に圧縮されたデータはエントロピーが高く、 さらなる圧縮はほぼ不可能。 「圧縮できる」=「データに冗長性がある」ということです。
工場で 1 kHz の振動センサーが 100 台稼働する場合、 1 日 8.64 億行。 CSV のままなら 30 GB/日、 月 1 TB。
Q1. 結局どの圧縮方式が最適?
「分析データ+頻繁にクエリ」なら Parquet + snappy。「アーカイブ用」なら Parquet + zstd(level 9) または xz。 ストリーミング系は lz4。
Q2. CSV.gz vs Parquet、どちらが良い?
分析用途なら Parquet 一択。 列単位スキャン、 述語プッシュダウン、 統計情報、 全て備えていて、 多くは 5 倍以上速い。
Q3. 暗号化と併用するときの順序は?
「圧縮 → 暗号化」の順。 暗号化済データはエントロピーが高く圧縮できない。 ただし圧縮による情報漏洩攻撃(CRIME, BREACH 等)に注意。
Q4. dtype 最適化はどこまで効果ある?
SSDSE のような中小データでメモリが半分以下、 ストレージも 30% 減程度。 数百万行を超えると違いが顕著。 categorical 化は特に効果が大きい。
Q5. 1 ファイル何 MB が理想?
分析向け Parquet では 100 MB〜1 GB が目安。 小さすぎるとメタデータ操作が重く、 大きすぎると並列度が落ちる。