はい、承知いたしました。
データ分析を効率化するためのNumPyのflatten
メソッドに関する詳細な記事を作成します。約5000語のボリュームで、初心者から中級者までが深く理解できる内容を目指します。
データ分析を効率化!NumPyのflattenで配列操作をマスターしよう
はじめに:データの世界とNumPyの羅針盤
現代は「データの時代」と呼ばれ、ビジネスの意思決定から科学技術の発展、さらには私たちの日常生活に至るまで、あらゆる場面でデータが活用されています。このデータという広大な海を航海するために、データサイエンティストやエンジニアが羅針盤として頼りにするのが、Pythonの数値計算ライブラリNumPyです。
NumPyの核心は、ndarray
(N-dimensional array)と呼ばれる強力な多次元配列オブジェクトにあります。画像データ(縦×横×色)、時系列データ(時間×特徴量)、実験データ(サンプル×測定項目×試行回数)など、私たちが扱うデータの多くは本質的に多次元です。NumPyは、これらの多次元配列を驚くほど高速かつ効率的に操作する機能を提供し、データ分析の生産性を飛躍的に向上させます。
多次元配列の操作は多岐にわたりますが、その中でも特に基本的かつ重要な操作の一つが「配列の1次元化(フラット化)」です。複雑な構造を持つ多次元配列を、シンプルな1本の線、つまり1次元配列に変換するこの操作は、一見地味に見えるかもしれません。しかし、実は多くのデータ分析タスクにおいて不可欠なステップなのです。
- 機械学習モデルへの入力: 多くの古典的な機械学習モデルやニューラルネットワークの全結合層は、1次元のベクトルデータ(特徴量ベクトル)を入力として受け取ります。画像やセンサーデータなどの多次元データをモデルに投入する前処理として、1次元化は必須の工程です。
- データ集計と可視化: 多次元配列に含まれる全要素の統計量(平均、中央値、分散など)を一度に計算したい場合や、全要素の分布をヒストグラムで確認したい場合、配列を1次元化すると処理が非常にシンプルになります。
- データ形式の変換: 異なるライブラリやシステム間でデータをやり取りする際、共通のフォーマットとして1次元配列が要求されることがあります。
この「1次元化」を実現するためのNumPyの代表的なメソッドが、今回主役となる numpy.ndarray.flatten()
です。この記事では、flatten()
メソッドを徹底的に解剖し、その使い方をマスターすることを目指します。
本記事を通じて、あなたは以下の知識を習得できます。
- NumPyにおける多次元配列の基本操作
flatten()
メソッドの基本的な使い方と、要素の並び順を制御するorder
引数の詳細flatten()
と非常によく似たravel()
メソッドとの決定的な違い(コピー vs ビュー)- もう一つの1次元化手法
reshape(-1)
との比較と、適切な使い分け - 画像処理や特徴量エンジニアリングなど、実践的なデータ分析シナリオにおける
flatten()
の活用例
この記事を読み終える頃には、あなたは単にflatten()
の使い方を知っているだけでなく、「なぜこの場面ではflatten()
を選ぶのか」「パフォーマンスを考慮するならどのメソッドが最適か」を自信を持って判断できるようになっているでしょう。さあ、NumPyの配列操作の奥深い世界へ、共に旅立ちましょう。
1. ウォーミングアップ:NumPyと多次元配列の基本
本題に入る前に、NumPyとndarray
の基本をおさらいしておきましょう。すでにNumPyに慣れている方は、このセクションを読み飛ばしていただいても構いません。
1.1. NumPyとは?
NumPy(Numerical Pythonの略)は、Pythonで科学技術計算を行うための基本的なパッケージです。その中核をなすのは、以下の特徴を持つndarray
オブジェクトです。
- 高速性: NumPyの配列は、C言語で実装された固定サイズのコンテナです。要素はメモリ上に連続して配置されるため、Pythonの標準のリストに比べて要素へのアクセスや演算が非常に高速です。
- 多次元対応: 1次元(ベクトル)、2次元(行列)、3次元(テンソル)など、任意の次元数の配列を扱うことができます。
- ブロードキャスティング: 形状(shape)が異なる配列間でも、ルールに従って自動的に形状を合わせて計算を行う強力な機能です。
- 豊富な数学関数: 線形代数、フーリエ変換、乱数生成など、高度な数学関数が組み込まれています。
まずはNumPyをインポートし、慣例に従ってnp
という別名をつけます。
python
import numpy as np
1.2. 多次元配列の作成
NumPy配列は様々な方法で作成できます。最も基本的なのは、Pythonのリストやタプルからnp.array()
を使って作成する方法です。
“`python
1次元配列(ベクトル)
vec = np.array([1, 2, 3, 4, 5])
print(vec)
出力: [1 2 3 4 5]
2次元配列(行列)
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(matrix)
出力:
[[1 2 3]
[4 5 6]]
3次元配列(テンソル)
tensor = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(tensor)
出力:
[[[1 2]
[3 4]]
[[5 6]
[7 8]]]
“`
他にも、連番の配列を作成するnp.arange()
や、特定の値で満たされた配列を作成するnp.zeros()
, np.ones()
などもよく使われます。
“`python
0から9までの連番の配列
range_arr = np.arange(10)
print(range_arr)
出力: [0 1 2 3 4 5 6 7 8 9]
3×4の形状で、すべての要素が0の配列
zeros_arr = np.zeros((3, 4))
print(zeros_arr)
出力:
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
“`
1.3. 配列の属性
作成したndarray
オブジェクトは、自身の情報を保持する様々な属性を持っています。これらを理解することは、配列操作の基本です。
ndim
: 配列の次元数shape
: 各次元の要素数をタプルで表したもの(形状)size
: 配列の全要素数 (shape
の各要素の積)dtype
: 配列の要素のデータ型
先ほど作成したmatrix
を例に見てみましょう。
“`python
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(f”次元数 (ndim): {matrix.ndim}”)
出力: 次元数 (ndim): 2
print(f”形状 (shape): {matrix.shape}”)
出力: 形状 (shape): (2, 3)
print(f”全要素数 (size): {matrix.size}”)
出力: 全要素数 (size): 6
print(f”データ型 (dtype): {matrix.dtype}”)
出力: データ型 (dtype): int64
“`
matrix
は2次元で、形状は「2行3列」、全要素数は6、データ型は64ビット整数であることがわかります。これらの基本を頭に入れた上で、いよいよ本題のflatten()
メソッドに進みましょう。
2. numpy.ndarray.flatten()
の基本とorder
引数
flatten()
は、その名の通り、多次元のndarray
を平らに(flatに)する、つまり1次元配列に変換するためのメソッドです。
2.1. 基本的な使い方
使い方は非常にシンプルで、ndarray
オブジェクトに対して.flatten()
を呼び出すだけです。
“`python
2次元配列の準備
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
print(“元の配列:”)
print(matrix)
print(f”元の形状: {matrix.shape}\n”)
flatten() を使って1次元化
flattened_array = matrix.flatten()
print(“flatten()後の配列:”)
print(flattened_array)
print(f”flatten()後の形状: {flattened_array.shape}”)
**実行結果:**
元の配列:
[[1 2 3]
[4 5 6]]
元の形状: (2, 3)
flatten()後の配列:
[1 2 3 4 5 6]
flatten()後の形状: (6,)
``
[1, 2, 3, 4, 5, 6]
(2, 3)の形状を持つ2次元配列が、見事に(6,)の形状を持つ1次元配列に変換されました。要素がという順番で並んでいることに注目してください。これは、まず最初の行(
[1, 2, 3])を読み込み、次に2番目の行(
[4, 5, 6]`)を読み込んで連結した結果です。
3次元配列でも同様です。
“`python
3次元配列の準備
tensor = np.arange(1, 13).reshape(2, 2, 3) # 1から12までの数値で2x2x3の配列を作成
print(“元の配列:”)
print(tensor)
print(f”元の形状: {tensor.shape}\n”)
flatten() を使って1次元化
flattened_tensor = tensor.flatten()
print(“flatten()後の配列:”)
print(flattened_tensor)
print(f”flatten()後の形状: {flattened_tensor.shape}”)
**実行結果:**
元の配列:
[[[ 1 2 3]
[ 4 5 6]]
[[ 7 8 9]
[10 11 12]]]
元の形状: (2, 2, 3)
flatten()後の配列:
[ 1 2 3 4 5 6 7 8 9 10 11 12]
flatten()後の形状: (12,)
“`
この場合も、一番外側の次元から順に、行単位で要素が読み出されているのがわかります。
2.2. 要素の並び順を操る order
引数
先ほどの例では、要素は「行」を優先して読み出されていました。これを行優先順(row-major order)と呼び、プログラミング言語のC言語における配列のメモリレイアウトと同じであるため、C-style orderとも呼ばれます。flatten()
のデフォルトの挙動はこのC-style orderです。
しかし、場合によっては「列」を優先して読み出したいこともあります。例えば、[1, 4, 2, 5, 3, 6]
のような順序で1次元化したい場合です。これを列優先順(column-major order)と呼び、科学技術計算で古くから使われているFortran言語のスタイルであるため、Fortran-style orderとも呼ばれます。
flatten()
メソッドはorder
引数を使って、この読み出し順序を制御できます。
order
引数に指定できる値は以下の4つです。
* 'C'
: C-style(行優先順)。デフォルト。
* 'F'
: Fortran-style(列優先順)。
* 'A'
: 元の配列がメモリ上でFortran連続(F-contiguous)であれば'F'
、そうでなければ'C'
の順序になる。
* 'K'
: 元の配列の要素がメモリ上に配置されている物理的な順序を維持する。
'C'
と'F'
が最も重要なので、まずはこの2つの違いをしっかり理解しましょう。
“`python
matrix = np.array([[1, 2, 3],
[4, 5, 6]])
order=’C’ (デフォルト)
flatten_c = matrix.flatten(order=’C’)
print(f”order=’C’ (行優先): {flatten_c}”)
order=’F’
flatten_f = matrix.flatten(order=’F’)
print(f”order=’F’ (列優先): {flatten_f}”)
**実行結果:**
order=’C’ (行優先): [1 2 3 4 5 6]
order=’F’ (列優先): [1 4 2 5 3 6]
``
order=’C’
結果は一目瞭然です。
*では、
[1, 2, 3](1行目) →
[4, 5, 6](2行目) の順に読み込まれます。
order=’F’
*では、
[1, 4](1列目) →
[2, 5](2列目) →
[3, 6]` (3列目) の順に読み込まれます。
'A'
と'K'
について(少し高度な話)
'A'
と'K'
は、配列のメモリレイアウトに関連する少し高度なオプションです。NumPyの配列は、デフォルトではC-styleで行優先にメモリ上に配置されます(C連続)。しかし、Fortran-styleで作成したり、転置(.T
)したりすると、列優先のメモリレイアウト(F連続)になることがあります。
“`python
C連続の配列
c_contiguous_arr = np.array([[1, 2, 3], [4, 5, 6]], order=’C’)
F連続の配列
f_contiguous_arr = np.array([[1, 2, 3], [4, 5, 6]], order=’F’)
print(f”C連続配列のflags:\n{c_contiguous_arr.flags}\n”)
print(f”F連続配列のflags:\n{f_contiguous_arr.flags}\n”)
‘A’オプションの挙動
print(f”C連続配列にorder=’A’: {c_contiguous_arr.flatten(order=’A’)}”)
print(f”F連続配列にorder=’A’: {f_contiguous_arr.flatten(order=’A’)}”)
**実行結果:**
C連続配列のflags:
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
…
F連続配列のflags:
C_CONTIGUOUS : False
F_CONTIGUOUS : True
OWNDATA : True
…
C連続配列にorder=’A’: [1 2 3 4 5 6]
F連続配列にorder=’A’: [1 4 2 5 3 6]
``
order=’A’は、配列がC連続ならC-order、F連続ならF-orderのように振る舞います。
order=’K’は、スライシングなどで作られた非連続な配列の場合に真価を発揮しますが、ほとんどのケースでは
order=’A’と同じように考えて差し支えありません。通常は
‘C’か
‘F’`を明示的に指定することが多いでしょう。
3. 最重要ポイント:flatten()
vs ravel()
– コピーかビューか
NumPyで配列を1次元化する方法として、flatten()
と非常によく似たravel()
というメソッド(または関数 np.ravel()
)が存在します。この2つは機能的にほぼ同じに見えるため、多くの初学者が混同しがちです。しかし、この2つの間には決定的かつ極めて重要な違いがあります。
その違いとは、flatten()
は常に新しい配列(コピー)を返し、ravel()
は可能な限り元の配列のビュー(参照)を返すという点です。
この「コピー vs ビュー」の概念は、NumPyを効率的に使いこなす上で避けては通れない道です。
- コピー (Copy): 元の配列とは完全に独立した、新しいデータ領域を持つ配列を作成します。コピーされた配列の値を変更しても、元の配列には一切影響しません。メモリを新たに消費します。
- ビュー (View): 元の配列と同じデータ領域を共有(参照)します。形状やストライド(メモリ上で次の要素に進むためのバイト数)の情報だけが新しくなります。ビューの値を変更すると、元の配列の値も変更されます。メモリ消費が少なく、作成が高速です。
この違いを、具体的な実験で確認してみましょう。
3.1. 実験1: flatten()
は安全な「コピー」を返す
まず、flatten()
を使った場合の挙動を見てみます。
“`python
元の配列を作成
original_array_1 = np.arange(9).reshape(3, 3)
print(“元の配列:”)
print(original_array_1)
print(“-” * 20)
flatten()で1次元化
flattened_array = original_array_1.flatten()
print(“flatten()で作成した配列:”)
print(flattened_array)
print(“-” * 20)
flatten()で作成した配列の最初の要素を99に変更
flattened_array[0] = 99
print(“変更後のflattened配列:”)
print(flattened_array)
print(“-” * 20)
元の配列がどうなったか確認
print(“変更後の「元の配列」:”)
print(original_array_1)
**実行結果:**
元の配列:
[[0 1 2]
[3 4 5]
[6 7 8]]
flatten()で作成した配列:
[0 1 2 3 4 5 6 7 8]
変更後のflattened配列:
[99 1 2 3 4 5 6 7 8]
変更後の「元の配列」:
[[0 1 2]
[3 4 5]
[6 7 8]]
``
flattened_arrayの値を変更しても、
original_array_1は全く影響を受けていません。これは、
flatten()が
original_array_1`のデータを丸ごとコピーして、新しい独立した配列を作成したことを意味します。これにより、元のデータを誤って変更してしまう心配がなく、安全に操作できます。
3.2. 実験2: ravel()
は危険だが高速な「ビュー」を返す
次に、ravel()
を使った場合を見てみましょう。
“`python
元の配列を作成
original_array_2 = np.arange(9).reshape(3, 3)
print(“元の配列:”)
print(original_array_2)
print(“-” * 20)
ravel()で1次元化
raveled_array = original_array_2.ravel()
print(“ravel()で作成した配列:”)
print(raveled_array)
print(“-” * 20)
ravel()で作成した配列の最初の要素を99に変更
raveled_array[0] = 99
print(“変更後のraveled配列:”)
print(raveled_array)
print(“-” * 20)
元の配列がどうなったか確認
print(“変更後の「元の配列」:”)
print(original_array_2)
**実行結果:**
元の配列:
[[0 1 2]
[3 4 5]
[6 7 8]]
ravel()で作成した配列:
[0 1 2 3 4 5 6 7 8]
変更後のraveled配列:
[99 1 2 3 4 5 6 7 8]
変更後の「元の配列」:
[[99 1 2]
[3 4 5]
[6 7 8]]
``
raveled_array
今度は、の値を変更したところ、
original_array_2の値まで変わってしまいました!これは、
raveled_arrayが
original_array_2`のビューであり、同じメモリ領域を指しているためです。
3.3. パフォーマンスの比較
なぜravel()
はビューを返すのでしょうか?それはパフォーマンスのためです。大規模な配列を扱う際、データを丸ごとコピーするのは時間とメモリの両面で大きなコストがかかります。ビューを返すravel()
は、これらのコストを劇的に削減できます。
%timeit
マジックコマンドを使って、その速度差を実際に計測してみましょう。
“`python
1000×1000の巨大な配列を作成
large_array = np.arange(1_000_000).reshape(1000, 1000)
flatten()の実行時間を計測
%timeit -n 100 large_array.flatten()
ravel()の実行時間を計測
%timeit -n 100 large_array.ravel()
**実行結果の例(環境により数値は異なります):**
100 loops, best of 5: 686 µs per loop # flatten()
100 loops, best of 5: 234 ns per loop # ravel()
``
ravel()
マイクロ秒(µs)とナノ秒(ns)の差に注目してください。1マイクロ秒は1000ナノ秒なので、この例ではが
flatten()の**約3000倍高速**に処理を終えています。
flatten()は100万個の要素をすべて新しいメモリ領域にコピーしているのに対し、
ravel()`は配列の形状情報を変更するだけの非常に軽い処理で済んでいるため、この圧倒的な速度差が生まれるのです。
3.4. ravel()
がコピーを返す場合
「ravel()
は可能な限りビューを返す」と説明しました。これは、常にビューを返すわけではないことを意味します。ravel()
がビューを返せるのは、元の配列の要素がメモリ上で1次元化しても連続性が保たれる場合です。
スライシングなどを使ってメモリ上で非連続な要素を取り出した配列の場合、ravel()
はビューを作成できず、flatten()
と同様にコピーを返します。
“`python
4×4の配列を作成
arr = np.arange(16).reshape(4, 4)
print(“元の配列:\n”, arr)
スライシングで偶数番目の列だけを取り出す(メモリ上で非連続になる)
sliced_arr = arr[:, ::2]
print(“\nスライス後の配列:\n”, sliced_arr)
print(“スライス後の配列はC連続か?:”, sliced_arr.flags[‘C_CONTIGUOUS’]) # Falseになる
この非連続な配列をravelする
raveled_sliced_arr = sliced_arr.ravel()
ravelされた配列の要素を変更
raveled_sliced_arr[0] = 99
スライス後の配列と元の配列を確認
print(“\n変更後のraveled配列:”, raveled_sliced_arr)
print(“変更後のスライス配列:\n”, sliced_arr)
print(“変更後の元の配列:\n”, arr)
**実行結果:**
元の配列:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
スライス後の配列:
[[ 0 2]
[ 4 6]
[ 8 10]
[12 14]]
スライス後の配列はC連続か?: False
変更後のraveled配列: [99 2 4 6 8 10 12 14]
変更後のスライス配列:
[[ 0 2]
[ 4 6]
[ 8 10]
[12 14]]
変更後の元の配列:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
``
raveled_sliced_arr
この例では、を変更しても、
sliced_arrや
arrは影響を受けませんでした。これは、
sliced_arrがメモリ上で非連続だったため、
ravel()`が仕方なくコピーを生成したためです。
3.5. flatten()
vs ravel()
使い分けの指針
-
flatten()
を使うべき時:- 安全性を最優先したい場合。元の配列を絶対に汚染したくない時。
- 1次元化した配列を、元の配列とは独立したオブジェクトとして長期間利用する場合。
- コードの意図として「ここはコピーである」ことを明確に示したい場合。
-
ravel()
を使うべき時:- パフォーマンス(速度・メモリ)が重要な場合。特に、巨大な配列を扱う時や、ループ内で繰り返し1次元化を行う時。
- 一時的に1次元配列として扱えればよく、元の配列が変更されても問題ない(または変更したい)場合。
- 関数の引数に渡すだけなど、短時間で使い捨てる場合。
この「コピー vs ビュー」の概念はNumPyの根幹をなすものであり、flatten
とravel
はその違いを理解するための格好の題材です。
4. もう一つの選択肢:reshape(-1)
との比較
配列を1次元化するもう一つの一般的な方法として、reshape()
メソッドがあります。
“`python
arr = np.array([[1, 2, 3], [4, 5, 6]])
reshaped_arr = arr.reshape(-1)
print(reshaped_arr)
出力: [1 2 3 4 5 6]
``
reshape()の引数に
-1`を指定すると、NumPyは他の次元のサイズから推測して、残りの次元のサイズを自動的に計算してくれます。この場合、1次元化されるので、全要素数(この例では6)を持つ1次元配列が作られます。
では、reshape(-1)
はflatten()
やravel()
とどう違うのでしょうか?
結論から言うと、reshape(-1)
の挙動はravel()
に非常に近いです。つまり、可能な限りビューを返します。
“`python
reshape(-1)がビューを返すか実験
original_array_3 = np.arange(9).reshape(3, 3)
reshaped_array = original_array_3.reshape(-1)
reshapeされた配列の要素を変更
reshaped_array[0] = 99
元の配列を確認
print(original_array_3)
**実行結果:**
[[99 1 2]
[ 3 4 5]
[ 6 7 8]]
``
ravel()と同様に、元の配列が変更されました。
reshape(-1)もビューを返していることがわかります。パフォーマンスも
ravel()`とほぼ同等で、非常に高速です。
ravel()
とreshape(-1)
の微妙な違い
ほとんどの場合、ravel()
とreshape(-1)
は同じように使えますが、微妙な違いも存在します。特にorder
引数の扱いです。ravel()
はorder='F'
を指定すると、F連続の配列に対して効率的に列優先のビューを返せます。一方、reshape(-1, order='F')
は、元の配列がF連続でない限り、ビューではなくコピーを生成する場合があります。これはreshape
がより汎用的な形状変更操作であるための制約です。
しかし、実用上は、order='C'
(デフォルト)で1次元化する限り、ravel()
とreshape(-1)
はほぼ同じものと考えて良いでしょう。どちらを選ぶかは、しばしばコーディングスタイルや好みの問題となります。
* ravel()
: 「1次元化する」という意図がメソッド名から明確。
* reshape(-1)
: より汎用的なreshape
操作の一環として1次元化を行う。他の形状変更コードと統一感が出る。
3つの手法の比較まとめ
ここで、3つの1次元化手法の特徴を表にまとめておきましょう。
機能/観点 | ndarray.flatten() |
ndarray.ravel() |
ndarray.reshape(-1) |
---|---|---|---|
返り値 | 常にコピー | 可能な限りビュー | 可能な限りビュー |
元データへの影響 | なし | あり(ビューの場合) | あり(ビューの場合) |
速度 | 遅い | 速い | 速い |
メモリ効率 | 悪い(追加メモリ要) | 良い | 良い |
メソッドの種類 | ndarrayのメソッドのみ | ndarrayのメソッド / トップレベル関数 | ndarrayのメソッドのみ |
主な用途/思想 | 安全性を確保し、独立した配列として操作 | パフォーマンスを優先し、高速・省メモリで処理 | 汎用的な形状変更の一環として1次元化 |
5. 実践!データ分析におけるflatten()
の応用例
理論を学んだところで、いよいよ実践的なシナリオでflatten
(およびその仲間たち)がどのように活躍するかを見ていきましょう。
5.1. 応用例1:画像データの前処理と機械学習
画像データは、典型的な多次元配列です。例えば、グレースケール画像は「高さ×幅」の2次元配列、カラー画像は「高さ×幅×色チャンネル(RGB)」の3次元配列として表現されます。
多くの機械学習モデル、特に古典的なものや、ニューラルネットワークの全結合層(Dense Layer)は、1次元のベクトルを入力として要求します。そのため、画像データをこれらのモデルに入力する前に、1次元化(フラット化)する必要があります。
ここでは、手書き数字データセット(MNISTなど)を想定した例を見てみましょう。28×28ピクセルのグレースケール画像があるとします。
“`python
import matplotlib.pyplot as plt
28×28の手書き数字画像を模したダミーデータを作成
(実際には scikit-learn の datasets.load_digits() などで読み込む)
dummy_image = np.random.rand(28, 28)
元の画像データを表示
plt.imshow(dummy_image, cmap=’gray’)
plt.title(f”Original Image\nShape: {dummy_image.shape}”)
plt.show()
画像データを1次元ベクトルに変換
この場合、元の画像データを保持したいため、コピーを生成するflatten()が適している
feature_vector = dummy_image.flatten()
print(f”Flatten後の特徴量ベクトルの形状: {feature_vector.shape}”)
print(f”28 * 28 = {28*28}”)
print(“Flatten後のデータ(先頭10件):”, feature_vector[:10])
**実行結果:**
Flatten後の特徴量ベクトルの形状: (784,)
28 * 28 = 784
Flatten後のデータ(先頭10件): [0.1378… 0.0388… … ]
“`
28×28の2次元配列が、784個の要素を持つ1次元の「特徴量ベクトル」に変換されました。このベクトルを、サポートベクターマシンや多層パーセプトロンなどの分類器に入力して、画像がどの数字(0〜9)であるかを学習させることができます。
このシナリオでflatten()
が好まれるのは、元の画像データを加工せずに残しておきたいという要求があるためです。ravel()
を使ってしまうと、後続の処理で特徴量ベクトルを正規化したりすると、元の画像データまで変わってしまう危険性があります。
5.2. 応用例2:データの集計と可視化
多次元配列の全要素にわたる分布を調べたい場合があります。例えば、ある地域の1年間の日ごとの気温を記録した12×31の2次元配列があったとします。この1年間の気温全体の分布をヒストグラムで可視化したい場合、配列を1次元化すると非常に簡単です。
“`python
1年間の気温データを模したダミーデータ(12ヶ月 x 31日)
季節変動を少し加味してみる
monthly_avg = np.array([5, 6, 10, 15, 20, 24, 28, 27, 23, 17, 12, 7])
ブロードキャストを利用して月ごとの平均気温に乱数を加える
temperature_data = monthly_avg[:, np.newaxis] + np.random.randn(12, 31) * 3
print(f”元のデータの形状: {temperature_data.shape}”)
全ての日の気温データを1次元化してヒストグラムを作成
ここでは一時的な利用なので、高速なravel()が適している
all_temperatures = temperature_data.ravel()
plt.hist(all_temperatures, bins=30, edgecolor=’black’)
plt.title(“Histogram of Yearly Temperatures”)
plt.xlabel(“Temperature (°C)”)
plt.ylabel(“Frequency (Days)”)
plt.grid(True, alpha=0.5)
plt.show()
``
matplotlib.pyplot.hist()
この例では、に1次元配列を渡すだけで、簡単に年間の気温分布を可視化できました。この目的のためには、元の12x31のデータを保持する必要はなく、計算も一度きりです。したがって、メモリ効率が良く高速な
ravel()(または
reshape(-1)`)が最適な選択となります。
5.3. 応用例3:特徴量のフラット化
データ分析では、複数の特徴量が入れ子になった構造を持つことがあります。例えば、センサーデータで、各タイムステップにおいて3次元空間(x, y, z)の加速度が記録されているとします。データが (タイムステップ数, 3)
という形状の配列で、これを1つの長い特徴量ベクトルとして扱いたい場合があります。
“`python
10タイムステップ分の3次元加速度データを模したデータ
time_steps = 10
accel_data = np.random.randn(time_steps, 3) # (10, 3)の形状
print(“元の加速度データ (shape: {}):\n{}”.format(accel_data.shape, accel_data))
全てのデータを1つのベクトルにフラット化
例: [t0_x, t0_y, t0_z, t1_x, t1_y, t1_z, …]
flat_features_c = accel_data.flatten(order=’C’)
print(“\n行優先 (C-order) でフラット化 (shape: {}):\n{}”.format(flat_features_c.shape, flat_features_c))
順番を変えてフラット化
例: [t0_x, t1_x, …, t9_x, t0_y, t1_y, …, t9_y, …]
flat_features_f = accel_data.flatten(order=’F’)
print(“\n列優先 (F-order) でフラット化 (shape: {}):\n{}”.format(flat_features_f.shape, flat_features_f))
``
order
この例では、引数が重要な意味を持ちます。
order=’C’
*(デフォルト)では、時間ごと(行ごと)にx, y, z成分が並びます。
order=’F’`では、まず全時間のx成分、次に全時間のy成分、最後に全時間のz成分が並びます。
*
どちらの順序が適切かは、後続の分析やモデルの要件によって決まります。flatten
のorder
引数を使いこなすことで、こうしたデータ構造の変換を柔軟に行うことができます。
6. まとめ:あなたのNumPyツールボックスにflatten
を加えよう
この記事では、NumPyのflatten
メソッドを中心に、配列を1次元化する様々な手法を詳細に探求してきました。最後に、重要なポイントを振り返りましょう。
-
flatten()
は多次元配列を1次元化する:これは基本中の基本です。複雑なデータ構造を、分析やモデル化が容易なシンプルなベクトル形式に変換します。 -
order
引数で順序を制御できる:order='C'
(行優先、デフォルト)とorder='F'
(列優先)を使い分けることで、意図した通りの順序で要素を並べることができます。 -
最大の教訓は「コピー vs ビュー」:
flatten()
は常にコピーを返します。元のデータを保護したい場合に最適で、安全性が高い選択肢です。ravel()
とreshape(-1)
は可能な限りビューを返します。メモリを節約し、実行速度が非常に速いため、パフォーマンスが求められる場面で威力を発揮します。しかし、ビューを介して元のデータが意図せず変更されるリスクも伴います。
-
状況に応じて最適な手法を選択する:
- 安全性が欲しいなら
flatten()
- 速度とメモリ効率が欲しいなら
ravel()
またはreshape(-1)
- どちらを選ぶべきか迷ったら、「元のデータを変更したくないか?」を自問自答するのが良い出発点です。
- 安全性が欲しいなら
flatten
は、単に配列を平らにするだけの単純なツールではありません。その挙動、特にravel
との比較を通じて、NumPyの根底に流れる「コピーとビュー」という重要な概念、そしてメモリ効率とパフォーマンスに関する意識を学ぶことができます。この理解は、NumPyを使ったより高度なデータ操作や、大規模データ分析におけるパフォーマンスチューニングにおいて、必ずあなたの力となるでしょう。
データ分析の道は、こうした一つ一つのツールを深く理解し、その特性を最大限に引き出すことで切り拓かれます。今日学んだ知識をあなたのツールボックスに加え、より効率的で、より洗練されたデータ分析を実践してください。NumPyの広大な可能性を探る旅は、まだ始まったばかりです。