Python二次元配列完全攻略【list・NumPy操作を徹底マスター】

二次元配列は、表形式のデータ(行列、テーブル、画像データなど)を扱う際に必須の概念です。Python では標準のリスト(list)やNumPyを使って二次元配列を効率的に操作できます。本記事では、二次元配列の作成から応用まで、実用的なサンプルコードとともに徹底解説します。

二次元配列とは

二次元配列は、行(row)と列(column)で構成される表形式のデータ構造です。以下のような場面で活用されます:

  • データ分析: CSVファイル、スプレッドシート
  • 画像処理: ピクセルデータの操作
  • 数学・統計: 行列計算、データ変換
  • ゲーム開発: マップ、ゲーム盤の表現
  • 機械学習: 特徴量行列、重み行列

二次元配列の基本構造

   列0  列1  列2
行0 [ 1,  2,  3]
行1 [ 4,  5,  6]
行2 [ 7,  8,  9]

Pythonリストでの二次元配列

基本的な作成方法

# 基本的な二次元配列の作成
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
print(matrix[0])     # [1, 2, 3] (最初の行)
print(matrix[1][2])  # 6 (2行目3列目の要素)

リスト内包表記での作成

# 3x4の二次元配列を0で初期化
rows, cols = 3, 4
matrix = [[0 for _ in range(cols)] for _ in range(rows)]
print(matrix)  # [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]

# 連番で初期化
matrix = [[i*cols + j for j in range(cols)] for i in range(rows)]
print(matrix)  # [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]

よくある間違いと正しい方法

# ❌ 間違い: 浅いコピーで同じリストを参照
wrong = [[0] * 3] * 3
wrong[0][0] = 1
print(wrong)  # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] - 全行が変更される

# ✅ 正解: 各行を独立して作成
correct = [[0] * 3 for _ in range(3)]
correct[0][0] = 1
print(correct)  # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

基本的な操作

要素のアクセスと変更

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 要素の取得
print(matrix[1][2])  # 6

# 要素の変更
matrix[1][2] = 99
print(matrix)  # [[1, 2, 3], [4, 5, 99], [7, 8, 9]]

# 行全体の取得
row = matrix[0]
print(row)  # [1, 2, 3]

行と列のサイズ取得

matrix = [[1, 2, 3], [4, 5, 6]]
rows = len(matrix)        # 行数: 2
cols = len(matrix[0])     # 列数: 3 (最初の行の長さ)
print(f"サイズ: {rows}×{cols}")

全要素の表示

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 基本的な表示
for row in matrix:
    print(row)

# インデックス付きで表示
for i, row in enumerate(matrix):
    for j, value in enumerate(row):
        print(f"[{i}][{j}] = {value}")

二次元配列の操作

行の追加・削除

matrix = [[1, 2], [3, 4]]

# 行の追加
matrix.append([5, 6])
print(matrix)  # [[1, 2], [3, 4], [5, 6]]

# 特定位置に行を挿入
matrix.insert(1, [7, 8])
print(matrix)  # [[1, 2], [7, 8], [3, 4], [5, 6]]

# 行の削除
del matrix[1]
print(matrix)  # [[1, 2], [3, 4], [5, 6]]

列の操作

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 特定列の取得
col = [row[1] for row in matrix]
print(col)  # [2, 5, 8]

# 列の追加
for i, row in enumerate(matrix):
    row.append((i + 1) * 10)
print(matrix)  # [[1, 2, 3, 10], [4, 5, 6, 20], [7, 8, 9, 30]]

# 特定列の削除
for row in matrix:
    del row[1]
print(matrix)  # [[1, 3, 10], [4, 6, 20], [7, 9, 30]]

転置(行と列の入れ替え)

matrix = [[1, 2, 3], [4, 5, 6]]

# zip()を使った転置
transposed = list(zip(*matrix))
print(transposed)  # [(1, 4), (2, 5), (3, 6)]

# リストに変換
transposed = [list(row) for row in zip(*matrix)]
print(transposed)  # [[1, 4], [2, 5], [3, 6]]

NumPyでの二次元配列

NumPy配列の作成

import numpy as np

# リストから作成
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(matrix)
print(f"形状: {matrix.shape}")  # (2, 3)

# 特殊な配列の作成
zeros = np.zeros((3, 4))      # 3×4のゼロ配列
ones = np.ones((2, 3))        # 2×3の1配列
identity = np.eye(3)          # 3×3の単位行列

NumPyの基本操作

import numpy as np

matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 要素アクセス
print(matrix[1, 2])  # 6

# スライス
print(matrix[0:2, 1:3])  # [[2 3] [5 6]]

# 条件による選択
print(matrix[matrix > 5])  # [6 7 8 9]

NumPyでの数学演算

import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

# 要素ごとの演算
print(a + b)     # [[ 6  8] [10 12]]
print(a * b)     # [[ 5 12] [21 32]]

# 行列積
print(a @ b)     # [[19 22] [43 50]]
print(np.dot(a, b))  # 同上

# 統計計算
print(np.sum(a))      # 10 (全要素の合計)
print(np.mean(a))     # 2.5 (平均)
print(np.max(a, axis=0))  # [3 4] (列ごとの最大値)

実用的な応用例

CSVデータの処理

import csv

def read_csv_as_2d_array(filename):
    with open(filename, 'r', encoding='utf-8') as file:
        reader = csv.reader(file)
        return [row for row in reader]

def write_2d_array_to_csv(data, filename):
    with open(filename, 'w', newline='', encoding='utf-8') as file:
        writer = csv.writer(file)
        writer.writerows(data)

# 使用例
data = [['名前', '年齢', '職業'], ['田中', '30', 'エンジニア'], ['佐藤', '25', 'デザイナー']]
write_2d_array_to_csv(data, 'employees.csv')
loaded_data = read_csv_as_2d_array('employees.csv')
print(loaded_data)

画像データの表現

import numpy as np

# グレースケール画像(8×8ピクセル)のシミュレーション
image = np.random.randint(0, 256, (8, 8), dtype=np.uint8)
print(f"画像サイズ: {image.shape}")

# 明度の統計
print(f"平均明度: {np.mean(image):.1f}")
print(f"最大明度: {np.max(image)}")

# 二値化処理
binary_image = (image > 128).astype(np.uint8) * 255
print(f"二値化後の一意値: {np.unique(binary_image)}")

ゲーム盤の表現

# 三目並べ(Tic-Tac-Toe)のゲーム盤
class TicTacToe:
    def __init__(self):
        self.board = [[' ' for _ in range(3)] for _ in range(3)]
    
    def display(self):
        for row in self.board:
            print('|'.join(row))
            print('-' * 5)
    
    def make_move(self, row, col, player):
        if self.board[row][col] == ' ':
            self.board[row][col] = player
            return True
        return False
    
    def check_winner(self):
        # 行のチェック
        for row in self.board:
            if row[0] == row[1] == row[2] != ' ':
                return row[0]
        
        # 列のチェック
        for col in range(3):
            if self.board[0][col] == self.board[1][col] == self.board[2][col] != ' ':
                return self.board[0][col]
        
        return None

# 使用例
game = TicTacToe()
game.make_move(0, 0, 'X')
game.make_move(1, 1, 'O')
game.display()

データ分析での活用

import numpy as np

# 売上データ(月×商品)
sales_data = np.array([
    [100, 150, 200],  # 1月の商品A, B, C
    [120, 130, 180],  # 2月
    [140, 170, 220],  # 3月
    [110, 160, 190]   # 4月
])

# 月ごとの売上合計
monthly_total = np.sum(sales_data, axis=1)
print(f"月ごと売上: {monthly_total}")

# 商品ごとの売上合計
product_total = np.sum(sales_data, axis=0)
print(f"商品ごと売上: {product_total}")

# 成長率の計算
growth_rate = (sales_data[-1] - sales_data[0]) / sales_data[0] * 100
print(f"成長率: {growth_rate.round(1)}%")

パフォーマンス比較

リスト vs NumPyの性能

import numpy as np
import time

# データの準備
size = 1000
list_2d = [[i + j for j in range(size)] for i in range(size)]
numpy_2d = np.arange(size * size).reshape(size, size)

# リストでの合計計算
start = time.time()
list_sum = sum(sum(row) for row in list_2d)
list_time = time.time() - start

# NumPyでの合計計算
start = time.time()
numpy_sum = np.sum(numpy_2d)
numpy_time = time.time() - start

print(f"リスト処理時間: {list_time:.4f}秒")
print(f"NumPy処理時間: {numpy_time:.4f}秒")
print(f"NumPyは{list_time/numpy_time:.1f}倍高速")

メモリ効率的な操作

ジェネレーターを使った大容量データ処理

def generate_large_matrix(rows, cols):
    """大きな行列を行ごとに生成"""
    for i in range(rows):
        yield [i * cols + j for j in range(cols)]

def process_matrix_by_row(matrix_generator):
    """行ごとに処理してメモリ使用量を削減"""
    row_sums = []
    for row in matrix_generator:
        row_sums.append(sum(row))
    return row_sums

# 使用例
large_matrix = generate_large_matrix(1000, 100)
results = process_matrix_by_row(large_matrix)
print(f"最初の5行の合計: {results[:5]}")

スパース行列の表現

# 疎行列(ほとんどが0の行列)の効率的な表現
class SparseMatrix:
    def __init__(self, rows, cols):
        self.rows = rows
        self.cols = cols
        self.data = {}  # (row, col): value の辞書
    
    def set(self, row, col, value):
        if value != 0:
            self.data[(row, col)] = value
        elif (row, col) in self.data:
            del self.data[(row, col)]
    
    def get(self, row, col):
        return self.data.get((row, col), 0)
    
    def to_dense(self):
        """通常の二次元配列に変換"""
        matrix = [[0 for _ in range(self.cols)] for _ in range(self.rows)]
        for (row, col), value in self.data.items():
            matrix[row][col] = value
        return matrix

# 使用例
sparse = SparseMatrix(3, 3)
sparse.set(0, 0, 1)
sparse.set(1, 2, 5)
sparse.set(2, 1, 3)
print(sparse.to_dense())  # [[1, 0, 0], [0, 0, 5], [0, 3, 0]]

高度な操作テクニック

条件に基づく要素の操作

import numpy as np

matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

# 条件に基づく置換
matrix[matrix > 5] = 0
print(matrix)  # [[1 2 3] [4 5 0] [0 0 0]]

# 複数条件
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
matrix[(matrix > 3) & (matrix < 7)] = -1
print(matrix)  # [[ 1  2  3] [-1 -1 -1] [ 7  8  9]]

行列の結合と分割

import numpy as np

a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])

# 水平結合
h_concat = np.hstack([a, b])
print(h_concat)  # [[1 2 5 6] [3 4 7 8]]

# 垂直結合
v_concat = np.vstack([a, b])
print(v_concat)  # [[1 2] [3 4] [5 6] [7 8]]

# 分割
matrix = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
left, right = np.hsplit(matrix, 2)
print(left)   # [[1 2] [5 6]]
print(right)  # [[3 4] [7 8]]

ソートとランキング

import numpy as np

matrix = np.array([[3, 1, 4], [1, 5, 9], [2, 6, 5]])

# 各行をソート
sorted_rows = np.sort(matrix, axis=1)
print(sorted_rows)  # [[1 3 4] [1 5 9] [2 5 6]]

# 各列をソート
sorted_cols = np.sort(matrix, axis=0)
print(sorted_cols)  # [[1 1 4] [2 5 5] [3 6 9]]

# ソートインデックス
indices = np.argsort(matrix, axis=1)
print(indices)  # [[1 0 2] [0 1 2] [0 2 1]]

エラーハンドリング

安全な配列アクセス

def safe_get(matrix, row, col, default=None):
    """安全な配列要素アクセス"""
    try:
        if 0 <= row < len(matrix) and 0 <= col < len(matrix[0]):
            return matrix[row][col]
        return default
    except (IndexError, TypeError):
        return default

def safe_set(matrix, row, col, value):
    """安全な配列要素設定"""
    try:
        if 0 <= row < len(matrix) and 0 <= col < len(matrix[0]):
            matrix[row][col] = value
            return True
    except (IndexError, TypeError):
        pass
    return False

# 使用例
matrix = [[1, 2, 3], [4, 5, 6]]
print(safe_get(matrix, 1, 2))     # 6
print(safe_get(matrix, 5, 0, -1)) # -1 (存在しない要素)

不正な配列のチェック

def validate_2d_array(matrix):
    """二次元配列の妥当性をチェック"""
    if not isinstance(matrix, list) or not matrix:
        return False, "空のリストまたはリストではありません"
    
    if not all(isinstance(row, list) for row in matrix):
        return False, "すべての行がリストである必要があります"
    
    first_len = len(matrix[0])
    if not all(len(row) == first_len for row in matrix):
        return False, "すべての行の長さが同じである必要があります"
    
    return True, "有効な二次元配列です"

# テスト
test_cases = [
    [[1, 2], [3, 4]],           # 有効
    [[1, 2], [3, 4, 5]],        # 無効(行の長さが異なる)
    [1, 2, 3],                  # 無効(一次元配列)
    []                          # 無効(空配列)
]

for i, test in enumerate(test_cases):
    valid, message = validate_2d_array(test)
    print(f"テスト{i+1}: {message}")

実装パターンと最適化

効率的な初期化パターン

import numpy as np

# パターン1: リスト内包表記(中程度のサイズ)
def create_matrix_list(rows, cols, value=0):
    return [[value for _ in range(cols)] for _ in range(rows)]

# パターン2: NumPy(大きなサイズ、数値計算)
def create_matrix_numpy(rows, cols, value=0):
    return np.full((rows, cols), value)

# パターン3: 条件に基づく初期化
def create_checkerboard(size):
    return [[(i + j) % 2 for j in range(size)] for i in range(size)]

# 使用例
board = create_checkerboard(8)
for row in board[:3]:  # 最初の3行を表示
    print(row)

まとめ

Python での二次元配列操作は、標準のリストと NumPy を適切に使い分けることで効率的に実行できます。用途に応じて最適な手法を選択することが重要です。

使い分けの指針:

  • 小〜中規模データ: Python リスト
  • 大規模数値データ: NumPy
  • 数学計算が必要: NumPy
  • メモリ効率重視: ジェネレーターや疎行列
  • 互換性重視: Python リスト

重要なポイント:

  • 浅いコピーの罠を避ける正しい初期化
  • インデックスエラーを防ぐ安全なアクセス
  • 適切なデータ構造の選択でパフォーマンス向上
  • NumPy のベクトル化で高速処理を実現

本記事のサンプルコードを参考に、あなたのプロジェクトに最適な二次元配列処理を実装してください。適切な手法の選択により、効率的で保守性の高いコードを作成できます。

参考文献

  • Python公式ドキュメント
  • NumPy公式ドキュメント
  • Python Data Science Handbook
  • Effective Python

らくらくPython塾 – 読むだけでマスター

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座