Python random.choice・sample・choices完全ガイド – ランダム選択の使い分けを徹底解説

Pythonのrandomモジュールには、リストや配列からランダムに要素を選択するための3つの主要な関数があります:choice()sample()choices()。それぞれ異なる特徴と用途を持ち、適切に使い分けることで効率的なランダム処理が可能になります。この記事では、これら3つの関数の違いから実用的な応用例まで、詳しく解説します。

1. 3つの関数の基本的な違い

関数 用途 重複 復元抽出 重み付け
choice() 1つ選択 なし なし なし
sample() 複数選択 なし なし なし
choices() 複数選択 あり あり あり

2. random.choice() – 1つの要素をランダム選択

基本的な使い方

import random

fruits = ["apple", "banana", "orange", "grape"]
selected = random.choice(fruits)
print(selected)  # "banana"(ランダムに1つ選択)

数値リストからの選択

import random

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
lucky_number = random.choice(numbers)
print(f"ラッキーナンバー: {lucky_number}")

文字列からの文字選択

import random

alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
random_char = random.choice(alphabet)
print(f"ランダム文字: {random_char}")

3. random.sample() – 重複なしで複数選択

基本的な使い方

import random

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
selected = random.sample(numbers, 3)
print(selected)  # [7, 2, 9](重複なしで3つ選択)

パスワード生成への応用

import random
import string

def generate_password(length=8):
    chars = string.ascii_letters + string.digits + "!@#$%&"
    return ''.join(random.sample(chars, length))

password = generate_password(12)
print(f"生成されたパスワード: {password}")

リスト全体のシャッフル

import random

deck = ["♠A", "♠K", "♠Q", "♠J", "♠10", "♥A", "♥K", "♥Q"]
shuffled = random.sample(deck, len(deck))
print(f"シャッフル後: {shuffled}")

# random.shuffle()と同等だが新しいリストを返す

4. random.choices() – 重複ありで複数選択(重み付け可能)

基本的な使い方

import random

colors = ["red", "blue", "green", "yellow"]
selected = random.choices(colors, k=5)
print(selected)  # ['blue', 'red', 'blue', 'green', 'red'](重複あり)

重み付けを使った選択

import random

items = ["Common", "Rare", "Epic", "Legendary"]
weights = [70, 20, 8, 2]  # 確率の重み

result = random.choices(items, weights=weights, k=10)
print(result)
# Commonが多く選ばれ、Legendaryは稀に選ばれる

確率分布による選択

import random

# サイコロの出目(重み付け)
dice_faces = [1, 2, 3, 4, 5, 6]
# 通常のサイコロ(均等確率)
normal_dice = random.choices(dice_faces, k=100)

# いかさまサイコロ(6が出やすい)
loaded_weights = [1, 1, 1, 1, 1, 5]
loaded_dice = random.choices(dice_faces, weights=loaded_weights, k=100)

print(f"6の出現回数(通常): {normal_dice.count(6)}")
print(f"6の出現回数(いかさま): {loaded_dice.count(6)}")

5. 実用的な応用例

クイズアプリケーション

import random

class QuizApp:
    def __init__(self):
        self.questions = [
            {"q": "Pythonの作者は?", "a": "Guido van Rossum"},
            {"q": "1 + 1 = ?", "a": "2"},
            {"q": "日本の首都は?", "a": "東京"},
            {"q": "2の3乗は?", "a": "8"}
        ]
    
    def get_random_question(self):
        return random.choice(self.questions)
    
    def get_quiz_set(self, num_questions):
        return random.sample(self.questions, min(num_questions, len(self.questions)))

quiz = QuizApp()
question = quiz.get_random_question()
print(f"問題: {question['q']}")

quiz_set = quiz.get_quiz_set(2)
for i, q in enumerate(quiz_set, 1):
    print(f"{i}. {q['q']}")

ゲームのアイテムドロップシステム

import random

class ItemDrop:
    def __init__(self):
        self.items = {
            "ポーション": {"weight": 50, "rarity": "Common"},
            "マジックソード": {"weight": 20, "rarity": "Rare"},
            "ドラゴンアーマー": {"weight": 5, "rarity": "Epic"},
            "伝説の指輪": {"weight": 1, "rarity": "Legendary"}
        }
    
    def drop_items(self, num_drops=3):
        item_names = list(self.items.keys())
        weights = [self.items[item]["weight"] for item in item_names]
        return random.choices(item_names, weights=weights, k=num_drops)

drop_system = ItemDrop()
dropped = drop_system.drop_items(5)
print(f"ドロップアイテム: {dropped}")

A/Bテストのユーザー振り分け

import random

class ABTestAssigner:
    def __init__(self, variant_weights=None):
        self.variants = ["A", "B"]
        self.weights = variant_weights or [50, 50]  # デフォルトは50:50
    
    def assign_user(self, user_id):
        # ユーザーIDをシードにして再現可能な振り分け
        random.seed(user_id)
        variant = random.choices(self.variants, weights=self.weights)[0]
        random.seed()  # シードをリセット
        return variant
    
    def assign_multiple_users(self, user_ids):
        return [self.assign_user(uid) for uid in user_ids]

# 70:30の比率でテスト
ab_test = ABTestAssigner([70, 30])
user_ids = range(1, 101)
assignments = ab_test.assign_multiple_users(user_ids)

print(f"グループAの人数: {assignments.count('A')}")
print(f"グループBの人数: {assignments.count('B')}")

6. データサンプリングと統計処理

データセットからのランダムサンプリング

import random

def create_sample_dataset():
    """サンプルデータセットを作成"""
    data = []
    for i in range(1000):
        data.append({
            "id": i,
            "age": random.randint(18, 80),
            "income": random.randint(200, 1000) * 1000,
            "category": random.choice(["A", "B", "C"])
        })
    return data

def analyze_sample(dataset, sample_size=100):
    """ランダムサンプルを分析"""
    sample = random.sample(dataset, sample_size)
    
    avg_age = sum(person["age"] for person in sample) / len(sample)
    avg_income = sum(person["income"] for person in sample) / len(sample)
    
    return {
        "sample_size": len(sample),
        "average_age": round(avg_age, 1),
        "average_income": round(avg_income, 0)
    }

dataset = create_sample_dataset()
analysis = analyze_sample(dataset, 50)
print(f"サンプル分析結果: {analysis}")

重み付きランダムサンプリング

import random

def weighted_sampling(population, weights, k):
    """重み付きサンプリング(復元抽出)"""
    return random.choices(population, weights=weights, k=k)

def weighted_sampling_no_replacement(population, weights, k):
    """重み付きサンプリング(非復元抽出)"""
    selected = []
    pop_copy = population.copy()
    weights_copy = weights.copy()
    
    for _ in range(min(k, len(pop_copy))):
        chosen = random.choices(pop_copy, weights=weights_copy)[0]
        idx = pop_copy.index(chosen)
        selected.append(pop_copy.pop(idx))
        weights_copy.pop(idx)
    
    return selected

# 使用例
cities = ["東京", "大阪", "名古屋", "福岡", "札幌"]
populations = [1400, 880, 230, 160, 190]  # 人口(万人)

# 人口に比例した重み付きサンプリング
sample1 = weighted_sampling(cities, populations, 10)
sample2 = weighted_sampling_no_replacement(cities, populations, 3)

print(f"復元抽出: {sample1}")
print(f"非復元抽出: {sample2}")

7. シミュレーション応用

モンテカルロシミュレーション

import random
import math

def estimate_pi(num_points=100000):
    """モンテカルロ法でπを推定"""
    inside_circle = 0
    
    for _ in range(num_points):
        x = random.uniform(-1, 1)
        y = random.uniform(-1, 1)
        if x**2 + y**2 <= 1:
            inside_circle += 1
    
    return 4 * inside_circle / num_points

estimated_pi = estimate_pi(1000000)
print(f"推定π値: {estimated_pi}")
print(f"実際のπ値: {math.pi}")
print(f"誤差: {abs(estimated_pi - math.pi):.6f}")

ランダムウォーク

import random

def random_walk_2d(steps=1000):
    """2次元ランダムウォーク"""
    x, y = 0, 0
    path = [(x, y)]
    
    directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]  # 上、右、下、左
    
    for _ in range(steps):
        dx, dy = random.choice(directions)
        x += dx
        y += dy
        path.append((x, y))
    
    return path

def analyze_random_walk(path):
    """ランダムウォークの分析"""
    start = path[0]
    end = path[-1]
    max_distance = max(abs(x) + abs(y) for x, y in path)
    
    return {
        "start": start,
        "end": end,
        "final_distance": abs(end[0]) + abs(end[1]),
        "max_distance": max_distance
    }

path = random_walk_2d(10000)
analysis = analyze_random_walk(path)
print(f"ランダムウォーク分析: {analysis}")

8. パフォーマンス比較と最適化

実行速度の比較

import random
import time

def performance_test():
    data = list(range(10000))
    iterations = 10000
    
    # choice()のテスト
    start = time.time()
    for _ in range(iterations):
        random.choice(data)
    choice_time = time.time() - start
    
    # sample()のテスト(1個選択)
    start = time.time()
    for _ in range(iterations):
        random.sample(data, 1)
    sample_time = time.time() - start
    
    # choices()のテスト(1個選択)
    start = time.time()
    for _ in range(iterations):
        random.choices(data, k=1)
    choices_time = time.time() - start
    
    print(f"choice(): {choice_time:.4f}秒")
    print(f"sample(): {sample_time:.4f}秒")
    print(f"choices(): {choices_time:.4f}秒")

performance_test()

メモリ効率的な大規模サンプリング

import random

def efficient_large_sampling(data_generator, sample_size):
    """大規模データからの効率的サンプリング(リザーバーサンプリング)"""
    reservoir = []
    
    for i, item in enumerate(data_generator()):
        if len(reservoir) < sample_size:
            reservoir.append(item)
        else:
            # ランダムに置き換え
            j = random.randint(0, i)
            if j < sample_size:
                reservoir[j] = item
    
    return reservoir

def large_data_generator():
    """大量データのジェネレータ"""
    for i in range(1000000):
        yield f"data_{i}"

# 100万件のデータから1000件をサンプリング
sample = efficient_large_sampling(large_data_generator, 1000)
print(f"サンプルサイズ: {len(sample)}")
print(f"最初の5件: {sample[:5]}")

9. エラーハンドリングとベストプラクティス

安全な選択関数

import random

def safe_choice(sequence, default=None):
    """空のシーケンスでもエラーにならないchoice"""
    try:
        return random.choice(sequence)
    except IndexError:
        return default

def safe_sample(sequence, k, default=None):
    """要求数がシーケンス長を超えてもエラーにならないsample"""
    try:
        return random.sample(sequence, k)
    except ValueError:
        return default or sequence.copy() if sequence else []

def safe_choices(sequence, k=1, weights=None, default=None):
    """空のシーケンスでもエラーにならないchoices"""
    if not sequence:
        return default or []
    
    try:
        return random.choices(sequence, weights=weights, k=k)
    except (ValueError, TypeError):
        return default or []

# 使用例
empty_list = []
short_list = [1, 2]

print(safe_choice(empty_list, "デフォルト値"))  # デフォルト値
print(safe_sample(short_list, 5))  # [1, 2](全要素返却)
print(safe_choices(empty_list, k=3, default=["なし"]))  # ["なし"]

10. 実践的なユースケース

機械学習のデータ分割

import random

def train_test_split(data, test_ratio=0.2, random_state=None):
    """データを訓練用とテスト用に分割"""
    if random_state:
        random.seed(random_state)
    
    data_copy = data.copy()
    random.shuffle(data_copy)
    
    split_point = int(len(data_copy) * (1 - test_ratio))
    train_data = data_copy[:split_point]
    test_data = data_copy[split_point:]
    
    return train_data, test_data

# サンプルデータ
dataset = [{"features": [i, i*2], "label": i % 2} for i in range(100)]

train, test = train_test_split(dataset, test_ratio=0.3, random_state=42)
print(f"訓練データ: {len(train)}件")
print(f"テストデータ: {len(test)}件")

ロードバランサーの実装

import random

class LoadBalancer:
    def __init__(self, servers, weights=None):
        self.servers = servers
        self.weights = weights or [1] * len(servers)
    
    def get_server_random(self):
        """ランダム選択"""
        return random.choice(self.servers)
    
    def get_server_weighted(self):
        """重み付き選択"""
        return random.choices(self.servers, weights=self.weights)[0]
    
    def get_servers_batch(self, count):
        """バッチ処理用の複数サーバー選択"""
        return random.choices(self.servers, weights=self.weights, k=count)

# 使用例
servers = ["server1", "server2", "server3", "server4"]
weights = [4, 3, 2, 1]  # server1が最も高性能

lb = LoadBalancer(servers, weights)

print("ランダム選択:")
for _ in range(5):
    print(f"  -> {lb.get_server_random()}")

print("\n重み付き選択:")
for _ in range(5):
    print(f"  -> {lb.get_server_weighted()}")

まとめ

Python の random.choice()random.sample()random.choices() は、それぞれ異なる特徴を持つ強力な関数です:

  • choice(): 1つの要素をシンプルに選択したい場合
  • sample(): 重複なしで複数の要素を選択したい場合
  • choices(): 重複ありで複数選択、または重み付け選択が必要な場合

適切な関数を選択することで、効率的で読みやすいコードを書くことができます。また、エラーハンドリングやパフォーマンスも考慮して、堅牢なアプリケーションを構築しましょう。

これらの関数をマスターすることで、シミュレーション、ゲーム開発、データ分析、機械学習など、様々な分野でランダム処理を効果的に活用できるようになります。

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

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

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

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

■テックジム東京本校

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

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

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

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