【Python入門】最頻値(mode)の求め方|データ分析で必須の統計処理を簡単に!

python

データ分析をしていると、よく出てくるのが「最頻値(mode)」という言葉。

最頻値とは、あるデータ集合で最も頻繁に現れる値のことです。

最頻値が活用される場面

  • アンケートで最も多かった回答
  • 商品の売れ筋カテゴリ
  • アクセスが多かった時間帯
  • 故障が多発する曜日
  • 最も利用される機能

代表値としての最頻値 統計学では、データの特徴を表す代表値として以下の3つがよく使われます:

代表値意味用途
平均値(mean)すべての値の合計を個数で割った値数値データの中心傾向
中央値(median)データを並べたときの真ん中の値外れ値に影響されにくい
最頻値(mode)最も多く現れる値カテゴリカルデータの分析

最頻値の特徴

  • 数値データだけでなく、文字列データにも適用可能
  • 複数の最頻値が存在する場合がある
  • データの分布の特徴を表す

この記事では、Pythonで最頻値を求める方法を標準ライブラリ・外部ライブラリの両面から丁寧に解説します。

スポンサーリンク

最頻値(mode)の基本概念

最頻値の定義と例

基本的な例

# 数値データの例
numbers = [1, 2, 2, 3, 3, 3, 4]
# 最頻値: 3(3回出現)

# 文字列データの例
colors = ['red', 'blue', 'red', 'green', 'red']
# 最頻値: 'red'(3回出現)

# 複数の最頻値の例
data = [1, 1, 2, 2, 3]
# 最頻値: 1と2(両方とも2回出現)

最頻値のパターン

1. 単一最頻値(Unimodal)

data = [1, 2, 2, 2, 3, 4]
# 最頻値: 2のみ

2. 複数最頻値(Multimodal)

data = [1, 1, 2, 2, 3]
# 最頻値: 1と2

3. 最頻値なし(No mode)

data = [1, 2, 3, 4, 5]
# すべての値が同じ頻度で出現

データ型による違い

# 整数データ
int_data = [1, 2, 2, 3]

# 浮動小数点データ
float_data = [1.5, 2.7, 2.7, 3.1]

# 文字列データ
str_data = ['apple', 'banana', 'apple', 'cherry']

# 日付データ
from datetime import date
date_data = [date(2024, 1, 1), date(2024, 1, 2), date(2024, 1, 1)]

標準ライブラリで最頻値を求める方法

statisticsモジュールの基本使用法

Python 3.4以降に標準で含まれるstatisticsモジュールを使った方法です。

基本的な使い方

import statistics

# 数値データの最頻値
numbers = [1, 2, 2, 3, 3, 3, 4]
mode_value = statistics.mode(numbers)
print(f"最頻値: {mode_value}")  # 最頻値: 3

# 文字列データの最頻値
fruits = ['apple', 'banana', 'apple', 'cherry', 'apple']
mode_fruit = statistics.mode(fruits)
print(f"最頻値: {mode_fruit}")  # 最頻値: apple

multimode関数(Python 3.8以降)

複数の最頻値を取得できる関数です。

import statistics

# 複数の最頻値がある場合
data = [1, 1, 2, 2, 3]
modes = statistics.multimode(data)
print(f"最頻値: {modes}")  # 最頻値: [1, 2]

# 単一の最頻値の場合
data2 = [1, 2, 2, 2, 3]
modes2 = statistics.multimode(data2)
print(f"最頻値: {modes2}")  # 最頻値: [2]

# すべて同じ頻度の場合
data3 = [1, 2, 3, 4]
modes3 = statistics.multimode(data3)
print(f"最頻値: {modes3}")  # 最頻値: [1, 2, 3, 4]

エラーハンドリング

statistics.mode()では、複数の最頻値がある場合にエラーが発生します。

import statistics

# エラーが発生するケース
data = [1, 1, 2, 2, 3]

try:
    mode_value = statistics.mode(data)
    print(f"最頻値: {mode_value}")
except statistics.StatisticsError as e:
    print(f"エラー: {e}")
    # エラー: no unique mode; found 2 equally common values

    # 代替案: multimodeを使用
    modes = statistics.multimode(data)
    print(f"複数の最頻値: {modes}")

実用的な例

成績データの分析

import statistics

# 5段階評価の成績データ
grades = [5, 4, 3, 5, 4, 5, 3, 5, 4, 5]

print(f"平均点: {statistics.mean(grades):.1f}")
print(f"中央値: {statistics.median(grades)}")
print(f"最頻値: {statistics.mode(grades)}")

# 実行結果:
# 平均点: 4.3
# 中央値: 4.5
# 最頻値: 5

collections.Counterを使った高度な分析

collections.Counterを使うと、より詳細な頻度分析が可能です。

基本的な使い方

from collections import Counter

# データの頻度を分析
data = [1, 2, 2, 3, 3, 3, 4, 4]
counter = Counter(data)

print("頻度分析結果:")
print(counter)  # Counter({3: 3, 2: 2, 4: 2, 1: 1})

# 最頻値を取得
most_common = counter.most_common(1)
print(f"最頻値: {most_common[0][0]}")  # 最頻値: 3

複数の最頻値を取得

from collections import Counter

def get_modes(data):
    """複数の最頻値を取得する関数"""
    if not data:
        return []
    
    counter = Counter(data)
    max_count = counter.most_common(1)[0][1]
    
    # 最大頻度と同じ頻度を持つすべての値を取得
    modes = [value for value, count in counter.items() if count == max_count]
    return modes

# 使用例
data1 = [1, 2, 2, 3, 3, 3, 4]
print(f"データ1の最頻値: {get_modes(data1)}")  # [3]

data2 = [1, 1, 2, 2, 3]
print(f"データ2の最頻値: {get_modes(data2)}")  # [1, 2]

data3 = [1, 2, 3, 4]
print(f"データ3の最頻値: {get_modes(data3)}")  # [1, 2, 3, 4]

詳細な頻度分析

from collections import Counter

def analyze_frequency(data, show_top=None):
    """データの頻度分析を行う関数"""
    counter = Counter(data)
    total_count = len(data)
    unique_values = len(counter)
    
    print(f"データ概要:")
    print(f"  総データ数: {total_count}")
    print(f"  ユニーク値数: {unique_values}")
    print()
    
    # 頻度順に表示
    most_common = counter.most_common(show_top)
    print("頻度分析結果:")
    for value, count in most_common:
        percentage = (count / total_count) * 100
        print(f"  {value}: {count}回 ({percentage:.1f}%)")
    
    # 最頻値の特定
    max_count = most_common[0][1]
    modes = [value for value, count in counter.items() if count == max_count]
    
    print(f"\n最頻値: {modes}")
    print(f"最頻値の出現回数: {max_count}")
    
    return counter, modes

# 使用例
survey_data = ['A', 'B', 'A', 'C', 'A', 'B', 'A', 'D', 'C', 'A']
analyze_frequency(survey_data, show_top=5)

文字列データの分析

from collections import Counter

# アンケート回答の分析
responses = [
    'とても満足', '満足', 'とても満足', '普通', 
    '満足', 'とても満足', '不満', '満足', '普通'
]

counter = Counter(responses)
print("アンケート結果:")
for response, count in counter.most_common():
    print(f"  {response}: {count}人")

print(f"\n最も多い回答: {counter.most_common(1)[0][0]}")

pandasを使った最頻値の処理

pandasを使うと、より高度なデータ処理が可能になります。

Series での最頻値取得

import pandas as pd

# Series での最頻値
data = pd.Series([1, 2, 2, 3, 3, 3, 4, 4])
modes = data.mode()

print("最頻値:")
print(modes)
print(f"最頻値のリスト: {modes.tolist()}")

# 複数の最頻値がある場合
data2 = pd.Series([1, 1, 2, 2, 3])
modes2 = data2.mode()
print(f"複数の最頻値: {modes2.tolist()}")

DataFrame での最頻値取得

import pandas as pd

# サンプルデータの作成
df = pd.DataFrame({
    'Category': ['A', 'B', 'A', 'C', 'A', 'B'],
    'Rating': [5, 4, 5, 3, 5, 4],
    'Region': ['East', 'West', 'East', 'North', 'East', 'West']
})

print("元データ:")
print(df)
print()

# 各列の最頻値
print("各列の最頻値:")
for column in df.columns:
    mode_values = df[column].mode()
    print(f"{column}: {mode_values.tolist()}")

グループ別の最頻値

import pandas as pd

# サンプルデータ
df = pd.DataFrame({
    'Department': ['Sales', 'Sales', 'Engineering', 'Engineering', 'Sales', 'Engineering'],
    'Grade': ['A', 'B', 'A', 'A', 'A', 'B'],
    'Score': [85, 90, 88, 92, 87, 89]
})

# 部署別の最頻グレード
dept_modes = df.groupby('Department')['Grade'].apply(lambda x: x.mode().tolist())
print("部署別最頻グレード:")
print(dept_modes)

値のカウントと可視化

import pandas as pd
import matplotlib.pyplot as plt

# データの作成
data = pd.Series([1, 2, 2, 3, 3, 3, 4, 4, 5])

# 値のカウント
value_counts = data.value_counts().sort_index()
print("値の出現回数:")
print(value_counts)

# 最頻値の特定
mode_value = data.mode()[0]
print(f"最頻値: {mode_value}")

# 簡単な可視化
plt.figure(figsize=(8, 6))
value_counts.plot(kind='bar')
plt.title('値の出現頻度')
plt.xlabel('値')
plt.ylabel('出現回数')
plt.axhline(y=value_counts.max(), color='red', linestyle='--', alpha=0.7, label=f'最大頻度 ({value_counts.max()})')
plt.legend()
plt.show()

実用的な最頻値分析の例

例1:Webアクセスログの分析

from datetime import datetime, timedelta
import random
from collections import Counter

# サンプルアクセスログの生成
def generate_access_log(days=30):
    """サンプルアクセスログを生成"""
    base_date = datetime(2024, 1, 1)
    log_data = []
    
    for day in range(days):
        current_date = base_date + timedelta(days=day)
        
        # 1日のアクセス数(平日は多め、休日は少なめ)
        if current_date.weekday() < 5:  # 平日
            daily_accesses = random.randint(100, 200)
        else:  # 休日
            daily_accesses = random.randint(50, 100)
        
        for _ in range(daily_accesses):
            # アクセス時間(業務時間帯に集中)
            if current_date.weekday() < 5:
                hour = random.choices(
                    range(24), 
                    weights=[1, 1, 1, 1, 1, 2, 3, 4, 5, 8, 10, 12, 
                            15, 18, 20, 18, 15, 12, 8, 5, 3, 2, 1, 1]
                )[0]
            else:
                hour = random.randint(0, 23)
            
            log_data.append({
                'date': current_date.date(),
                'hour': hour,
                'weekday': current_date.strftime('%A'),
                'page': random.choice(['home', 'products', 'about', 'contact', 'blog'])
            })
    
    return log_data

# ログデータの生成と分析
log_data = generate_access_log()

def analyze_access_patterns(log_data):
    """アクセスパターンを分析"""
    # 時間別アクセス分析
    hours = [entry['hour'] for entry in log_data]
    hour_counter = Counter(hours)
    
    print("=== アクセス時間分析 ===")
    print("最もアクセスが多い時間帯:")
    for hour, count in hour_counter.most_common(5):
        print(f"  {hour:2d}時台: {count}回")
    
    # 曜日別アクセス分析
    weekdays = [entry['weekday'] for entry in log_data]
    weekday_counter = Counter(weekdays)
    
    print("\n=== 曜日別アクセス分析 ===")
    for weekday, count in weekday_counter.most_common():
        print(f"  {weekday}: {count}回")
    
    # ページ別アクセス分析
    pages = [entry['page'] for entry in log_data]
    page_counter = Counter(pages)
    
    print("\n=== ページ別アクセス分析 ===")
    for page, count in page_counter.most_common():
        print(f"  {page}: {count}回")
    
    return {
        'peak_hour': hour_counter.most_common(1)[0],
        'peak_weekday': weekday_counter.most_common(1)[0],
        'popular_page': page_counter.most_common(1)[0]
    }

# 分析実行
results = analyze_access_patterns(log_data)
print(f"\n=== サマリー ===")
print(f"ピーク時間: {results['peak_hour'][0]}時台 ({results['peak_hour'][1]}回)")
print(f"最もアクセスが多い曜日: {results['peak_weekday'][0]} ({results['peak_weekday'][1]}回)")
print(f"最も人気のページ: {results['popular_page'][0]} ({results['popular_page'][1]}回)")

例2:顧客購買行動の分析

import pandas as pd
from collections import Counter
import random

# サンプル購買データの生成
def generate_purchase_data(num_customers=1000):
    """サンプル購買データを生成"""
    categories = ['Electronics', 'Clothing', 'Books', 'Home', 'Sports']
    age_groups = ['18-25', '26-35', '36-45', '46-55', '56+']
    
    data = []
    for customer_id in range(num_customers):
        # 顧客の年齢層
        age_group = random.choice(age_groups)
        
        # 購買履歴(顧客によって好みが異なる)
        purchases = []
        num_purchases = random.randint(1, 10)
        
        # 年齢層による購買傾向
        if age_group == '18-25':
            category_weights = [30, 40, 15, 10, 25]  # Electronics, Clothing重視
        elif age_group == '26-35':
            category_weights = [25, 30, 20, 15, 10]  # バランス型
        elif age_group == '36-45':
            category_weights = [20, 15, 25, 30, 10]  # Home, Books重視
        elif age_group == '46-55':
            category_weights = [15, 10, 30, 35, 10]  # Home重視
        else:  # 56+
            category_weights = [10, 5, 40, 30, 15]   # Books重視
        
        for _ in range(num_purchases):
            category = random.choices(categories, weights=category_weights)[0]
            purchases.append(category)
        
        data.append({
            'customer_id': customer_id,
            'age_group': age_group,
            'purchases': purchases,
            'favorite_category': Counter(purchases).most_common(1)[0][0]
        })
    
    return data

def analyze_purchase_behavior(purchase_data):
    """購買行動を分析"""
    # 年齢層別の最頻購買カテゴリ
    age_category_data = {}
    
    for age_group in ['18-25', '26-35', '36-45', '46-55', '56+']:
        age_customers = [c for c in purchase_data if c['age_group'] == age_group]
        all_purchases = []
        for customer in age_customers:
            all_purchases.extend(customer['purchases'])
        
        if all_purchases:
            category_counter = Counter(all_purchases)
            age_category_data[age_group] = category_counter
    
    print("=== 年齢層別購買傾向 ===")
    for age_group, counter in age_category_data.items():
        print(f"\n{age_group}歳:")
        for category, count in counter.most_common(3):
            percentage = (count / sum(counter.values())) * 100
            print(f"  {category}: {count}回 ({percentage:.1f}%)")
    
    # 全体の最頻カテゴリ
    all_purchases = []
    for customer in purchase_data:
        all_purchases.extend(customer['purchases'])
    
    overall_counter = Counter(all_purchases)
    print(f"\n=== 全体傾向 ===")
    print(f"最も人気のカテゴリ: {overall_counter.most_common(1)[0][0]}")
    
    return age_category_data, overall_counter

# 分析実行
purchase_data = generate_purchase_data()
age_trends, overall_trend = analyze_purchase_behavior(purchase_data)

例3:テキストデータの分析

import re
from collections import Counter

def analyze_text_patterns(text):
    """テキストの文字・単語パターンを分析"""
    
    # 文字レベルの分析
    char_counter = Counter(text.lower())
    
    # 英数字のみを対象
    alphanum_chars = {char: count for char, count in char_counter.items() 
                     if char.isalnum()}
    
    print("=== 文字頻度分析 ===")
    print("最も頻繁な文字:")
    for char, count in Counter(alphanum_chars).most_common(10):
        print(f"  '{char}': {count}回")
    
    # 単語レベルの分析
    words = re.findall(r'\b\w+\b', text.lower())
    word_counter = Counter(words)
    
    print("\n=== 単語頻度分析 ===")
    print("最も頻繁な単語:")
    for word, count in word_counter.most_common(10):
        print(f"  '{word}': {count}回")
    
    # 単語長の分析
    word_lengths = [len(word) for word in words]
    length_counter = Counter(word_lengths)
    
    print("\n=== 単語長分析 ===")
    print("最も多い単語長:")
    for length, count in length_counter.most_common(5):
        print(f"  {length}文字: {count}個")
    
    return char_counter, word_counter, length_counter

# サンプルテキストで分析
sample_text = """
Python is a high-level programming language. 
Python is widely used for data analysis, web development, and artificial intelligence.
The Python community is very active and supportive.
Python syntax is clear and readable, making it great for beginners.
"""

analyze_text_patterns(sample_text)

異なる手法の比較と使い分け

パフォーマンス比較

import time
import statistics
from collections import Counter
import pandas as pd
import random

def benchmark_mode_methods(data_size=10000, iterations=1000):
    """最頻値取得方法のパフォーマンス比較"""
    
    # テストデータ生成
    test_data = [random.randint(1, 100) for _ in range(data_size)]
    
    methods = {
        'statistics.mode': lambda data: statistics.mode(data),
        'statistics.multimode': lambda data: statistics.multimode(data)[0],
        'Counter': lambda data: Counter(data).most_common(1)[0][0],
        'pandas.mode': lambda data: pd.Series(data).mode().iloc[0]
    }
    
    results = {}
    
    for method_name, method_func in methods.items():
        start_time = time.time()
        
        for _ in range(iterations):
            try:
                result = method_func(test_data)
            except statistics.StatisticsError:
                # statistics.modeで複数最頻値がある場合
                result = statistics.multimode(test_data)[0]
        
        end_time = time.time()
        results[method_name] = end_time - start_time
    
    print(f"パフォーマンス比較 (データサイズ: {data_size}, 繰り返し: {iterations})")
    print("-" * 50)
    
    sorted_results = sorted(results.items(), key=lambda x: x[1])
    for method, time_taken in sorted_results:
        print(f"{method:<20}: {time_taken:.4f}秒")
    
    return results

# ベンチマーク実行
benchmark_results = benchmark_mode_methods()

使い分けガイド

def choose_mode_method(data, requirements):
    """
    要件に応じて最適な最頻値取得方法を提案
    
    Args:
        data: 分析対象データ
        requirements: 要件辞書
            - 'multiple_modes': 複数最頻値の取得が必要か
            - 'performance': パフォーマンス重視か
            - 'detailed_analysis': 詳細な頻度分析が必要か
            - 'integration': pandas DataFrameとの統合が必要か
    """
    
    print("=== 最頻値取得方法の推奨 ===")
    
    if requirements.get('detailed_analysis', False):
        print("推奨: collections.Counter")
        print("理由: 詳細な頻度分析と柔軟な操作が可能")
        
        counter = Counter(data)
        print("\n実行例:")
        print(f"Counter結果: {dict(counter.most_common(5))}")
        
    elif requirements.get('integration', False):
        print("推奨: pandas.Series.mode()")
        print("理由: pandas DataFrameとの統合が容易")
        
        import pandas as pd
        series = pd.Series(data)
        modes = series.mode()
        print(f"\n実行例: {modes.tolist()}")
        
    elif requirements.get('multiple_modes', False):
        print("推奨: statistics.multimode() (Python 3.8+)")
        print("理由: 複数最頻値を標準ライブラリで取得可能")
        
        import statistics
        modes = statistics.multimode(data)
        print(f"\n実行例: {modes}")
        
    elif requirements.get('performance', False):
        print("推奨: statistics.mode()")
        print("理由: 最も高速(単一最頻値の場合)")
        
        import statistics
        try:
            mode = statistics.mode(data)
            print(f"\n実行例: {mode}")
        except statistics.StatisticsError:
            print("注意: 複数最頻値があるため、statistics.multimodeを使用してください")
    
    else:
        print("推奨: statistics.mode() または statistics.multimode()")
        print("理由: 標準ライブラリで十分、シンプル")

# 使用例
sample_data = [1, 2, 2, 3, 3, 3, 4]

# シンプルな用途
choose_mode_method(sample_data, {'performance': True})

print("\n" + "="*50 + "\n")

# 詳細分析が必要な用途
choose_mode_method(sample_data, {'detailed_analysis': True})

エラーハンドリングとエッジケース

包括的なエラーハンドリング

import statistics
from collections import Counter
import pandas as pd

def safe_mode_calculation(data, method='auto'):
    """
    安全な最頻値計算(エラーハンドリング付き)
    
    Args:
        data: 分析対象データ
        method: 'auto', 'statistics', 'counter', 'pandas'
    
    Returns:
        dict: 結果辞書
    """
    
    result = {
        'success': False,
        'modes': [],
        'error': None,
        'method_used': method
    }
    
    # 入力検証
    if not data:
        result['error'] = "データが空です"
        return result
    
    if not isinstance(data, (list, tuple, pd.Series)):
        result['error'] = f"サポートされていないデータ型: {type(data)}"
        return result
    
    try:
        # データの前処理
        cleaned_data = [x for x in data if x is not None]
        
        if not cleaned_data:
            result['error'] = "有効なデータがありません(すべてNone)"
            return result
        
        if method == 'auto':
            # 自動選択ロジック
            if len(set(cleaned_data)) == len(cleaned_data):
                # すべて異なる値
                result['modes'] = cleaned_data
                result['method_used'] = 'auto (no mode)'
            else:
                # 最頻値が存在
                try:
                    mode = statistics.mode(cleaned_data)
                    result['modes'] = [mode]
                    result['method_used'] = 'statistics.mode'
                except statistics.StatisticsError:
                    # 複数最頻値の場合
                    modes = statistics.multimode(cleaned_data)
                    result['modes'] = modes
                    result['method_used'] = 'statistics.multimode'
        
        elif method == 'statistics':
            try:
                mode = statistics.mode(cleaned_data)
                result['modes'] = [mode]
            except statistics.StatisticsError:
                # Python 3.8以降でmultimodeを試行
                try:
                    modes = statistics.multimode(cleaned_data)
                    result['modes'] = modes
                except AttributeError:
                    # Python 3.7以前の場合
                    counter = Counter(cleaned_data)
                    max_count = max(counter.values())
                    modes = [k for k, v in counter.items() if v == max_count]
                    result['modes'] = modes
        
        elif method == 'counter':
            counter = Counter(cleaned_data)
            if not counter:
                result['error'] = "カウンターが空です"
                return result
            
            max_count = max(counter.values())
            modes = [k for k, v in counter.items() if v == max_count]
            result['modes'] = modes
        
        elif method == 'pandas':
            series = pd.Series(cleaned_data)
            modes = series.mode().tolist()
            result['modes'] = modes
        
        else:
            result['error'] = f"不明なメソッド: {method}"
            return result
        
        result['success'] = True
        
    except Exception as e:
        result['error'] = f"予期しないエラー: {str(e)}"
    
    return result

# テスト用関数
def test_safe_mode_calculation():
    """安全な最頻値計算のテスト"""
    
    test_cases = [
        # 正常ケース
        ([1, 2, 2, 3], "単一最頻値"),
        ([1, 1, 2, 2], "複数最頻値"),
        (['a', 'b', 'b', 'c'], "文字列データ"),
        
        # エッジケース
        ([], "空データ"),
        ([None, None, None], "すべてNone"),
        ([1, None, 2, None, 2], "Noneを含むデータ"),
        ([1, 2, 3, 4], "すべて異なる値"),
        ([5], "単一要素"),
        ([1, 1, 1, 1], "すべて同じ値"),
        
        # エラーケース
        ("string", "文字列(リストでない)"),
        (None, "None"),
    ]
    
    print("=== 安全な最頻値計算テスト ===")
    
    for data, description in test_cases:
        print(f"\nテスト: {description}")
        print(f"データ: {data}")
        
        result = safe_mode_calculation(data)
        
        if result['success']:
            print(f"✓ 成功: {result['modes']} (方法: {result['method_used']})")
        else:
            print(f"✗ エラー: {result['error']}")

# テスト実行
test_safe_mode_calculation()

データ型別の処理

from datetime import datetime, date, time
import numpy as np

def handle_different_data_types():
    """異なるデータ型での最頻値処理例"""
    
    # 日付データ
    dates = [date(2024, 1, 1), date(2024, 1, 2), date(2024, 1, 1), date(2024, 1, 3)]
    print("日付データの最頻値:")
    date_result = safe_mode_calculation(dates)
    print(f"結果: {date_result['modes']}")
    
    # 時刻データ
    times = [time(9, 0), time(10, 0), time(9, 0), time(11, 0)]
    print("\n時刻データの最頻値:")
    time_result = safe_mode_calculation(times)
    print(f"結果: {time_result['modes']}")
    
    # 浮動小数点数(精度に注意)
    floats = [1.1, 1.2, 1.1, 1.3]
    print("\n浮動小数点数の最頻値:")
    float_result = safe_mode_calculation(floats)
    print(f"結果: {float_result['modes']}")
    
    # 丸め誤差がある場合の対処
    problematic_floats = [0.1 + 0.2, 0.3, 0.1 + 0.2, 0.4]
    print("\n丸め誤差がある浮動小数点数:")
    print(f"元データ: {problematic_floats}")
    
    # 適切な精度で丸める
    rounded_floats = [round(x, 10) for x in problematic_floats]
    rounded_result = safe_mode_calculation(rounded_floats)
    print(f"丸め後の結果: {rounded_result['modes']}")
    
    # タプルデータ
    tuples = [(1, 2), (3, 4), (1, 2), (5, 6)]
    print("\nタプルデータの最頻値:")
    tuple_result = safe_mode_calculation(tuples)
    print(f"結果: {tuple_result['modes']}")

handle_different_data_types()

応用的な分析手法

条件付き最頻値分析

import pandas as pd
from collections import defaultdict

def conditional_mode_analysis(df, target_column, condition_columns):
    """
    条件付き最頻値分析
    
    Args:
        df: pandas DataFrame
        target_column: 最頻値を求める対象列
        condition_columns: 条件となる列のリスト
    """
    
    results = defaultdict(dict)
    
    # 条件列の組み合わせでグループ化
    grouped = df.groupby(condition_columns)
    
    for group_key, group_data in grouped:
        target_data = group_data[target_column].dropna()
        
        if len(target_data) > 0:
            modes = target_data.mode().tolist()
            
            # 条件の文字列表現を作成
            if isinstance(group_key, tuple):
                condition_str = " & ".join([f"{col}={val}" for col, val in zip(condition_columns, group_key)])
            else:
                condition_str = f"{condition_columns[0]}={group_key}"
            
            results[condition_str] = {
                'modes': modes,
                'count': len(target_data),
                'unique_values': target_data.nunique()
            }
    
    return dict(results)

# 使用例
sample_df = pd.DataFrame({
    'Department': ['Sales', 'Sales', 'Engineering', 'Engineering', 'Sales', 'Engineering', 'Marketing', 'Marketing'],
    'Level': ['Junior', 'Senior', 'Junior', 'Senior', 'Junior', 'Junior', 'Senior', 'Junior'],
    'Rating': ['Good', 'Excellent', 'Good', 'Excellent', 'Average', 'Good', 'Good', 'Average'],
    'Satisfaction': [4, 5, 4, 5, 3, 4, 4, 3]
})

print("=== 条件付き最頻値分析 ===")
print("サンプルデータ:")
print(sample_df)

print("\n部署別の最頻評価:")
dept_results = conditional_mode_analysis(sample_df, 'Rating', ['Department'])
for condition, result in dept_results.items():
    print(f"{condition}: {result['modes']} (n={result['count']})")

print("\n部署×レベル別の最頻評価:")
dept_level_results = conditional_mode_analysis(sample_df, 'Rating', ['Department', 'Level'])
for condition, result in dept_level_results.items():
    print(f"{condition}: {result['modes']} (n={result['count']})")

時系列データの最頻値分析

import pandas as pd
from datetime import datetime, timedelta

def time_series_mode_analysis(df, datetime_column, value_column, freq='D'):
    """
    時系列データの期間別最頻値分析
    
    Args:
        df: pandas DataFrame
        datetime_column: 日時列名
        value_column: 値列名
        freq: 集計頻度 ('D':日別, 'W':週別, 'M':月別)
    """
    
    # 日時列をインデックスに設定
    df_copy = df.copy()
    df_copy[datetime_column] = pd.to_datetime(df_copy[datetime_column])
    df_copy = df_copy.set_index(datetime_column)
    
    # 期間別にグループ化
    grouped = df_copy.groupby(pd.Grouper(freq=freq))
    
    results = []
    
    for period, group_data in grouped:
        if len(group_data) > 0:
            values = group_data[value_column].dropna()
            
            if len(values) > 0:
                modes = values.mode().tolist()
                
                results.append({
                    'period': period,
                    'modes': modes,
                    'total_count': len(values),
                    'unique_count': values.nunique(),
                    'most_common_count': values.value_counts().iloc[0] if len(values) > 0 else 0
                })
    
    return pd.DataFrame(results)

# サンプル時系列データの生成
dates = pd.date_range(start='2024-01-01', end='2024-01-31', freq='D')
sample_time_data = []

for date in dates:
    # 1日に複数のデータポイント
    daily_points = np.random.randint(5, 15)
    for _ in range(daily_points):
        # 時間帯による傾向
        hour = np.random.choice(range(24), p=[0.02, 0.01, 0.01, 0.01, 0.01, 0.02, 
                                             0.05, 0.08, 0.12, 0.15, 0.12, 0.10,
                                             0.08, 0.06, 0.05, 0.04, 0.03, 0.02,
                                             0.02, 0.02, 0.02, 0.02, 0.02, 0.02])
        
        timestamp = date + timedelta(hours=hour, minutes=np.random.randint(0, 60))
        
        # 曜日による傾向
        if date.weekday() < 5:  # 平日
            status = np.random.choice(['Active', 'Idle', 'Busy'], p=[0.6, 0.2, 0.2])
        else:  # 週末
            status = np.random.choice(['Active', 'Idle', 'Busy'], p=[0.3, 0.6, 0.1])
        
        sample_time_data.append({
            'timestamp': timestamp,
            'status': status
        })

time_df = pd.DataFrame(sample_time_data)

print("=== 時系列最頻値分析 ===")
print("日別最頻ステータス:")
daily_modes = time_series_mode_analysis(time_df, 'timestamp', 'status', freq='D')
print(daily_modes.head(10))

print("\n週別最頻ステータス:")
weekly_modes = time_series_mode_analysis(time_df, 'timestamp', 'status', freq='W')
print(weekly_modes)

階層データの最頻値分析

def hierarchical_mode_analysis(df, hierarchy_columns, target_column):
    """
    階層データの最頻値分析
    
    Args:
        df: pandas DataFrame
        hierarchy_columns: 階層を表す列のリスト(上位から下位の順)
        target_column: 分析対象列
    """
    
    results = {}
    
    # 各階層レベルでの分析
    for level in range(1, len(hierarchy_columns) + 1):
        current_hierarchy = hierarchy_columns[:level]
        level_name = " > ".join(current_hierarchy)
        
        grouped = df.groupby(current_hierarchy)
        level_results = {}
        
        for group_key, group_data in grouped:
            target_data = group_data[target_column].dropna()
            
            if len(target_data) > 0:
                modes = target_data.mode().tolist()
                
                if isinstance(group_key, tuple):
                    key_str = " > ".join(map(str, group_key))
                else:
                    key_str = str(group_key)
                
                level_results[key_str] = {
                    'modes': modes,
                    'count': len(target_data),
                    'distribution': target_data.value_counts().to_dict()
                }
        
        results[level_name] = level_results
    
    return results

# サンプル階層データ
hierarchical_df = pd.DataFrame({
    'Region': ['North', 'North', 'North', 'South', 'South', 'South', 'East', 'East'],
    'State': ['NY', 'NY', 'MA', 'TX', 'TX', 'FL', 'PA', 'PA'],
    'City': ['NYC', 'Albany', 'Boston', 'Houston', 'Dallas', 'Miami', 'Philly', 'Pittsburgh'],
    'Product': ['A', 'B', 'A', 'C', 'A', 'B', 'A', 'A'],
    'Rating': [5, 4, 5, 3, 4, 4, 5, 5]
})

print("=== 階層データ最頻値分析 ===")
hierarchy_results = hierarchical_mode_analysis(
    hierarchical_df, 
    ['Region', 'State', 'City'], 
    'Product'
)

for level, level_data in hierarchy_results.items():
    print(f"\n{level}レベル:")
    for group, group_data in level_data.items():
        print(f"  {group}: {group_data['modes']} (n={group_data['count']})")

可視化と報告

最頻値の可視化

import matplotlib.pyplot as plt
import seaborn as sns
from collections import Counter

def visualize_mode_analysis(data, title="最頻値分析", figsize=(12, 8)):
    """最頻値分析の可視化"""
    
    fig, axes = plt.subplots(2, 2, figsize=figsize)
    fig.suptitle(title, fontsize=16)
    
    # データの前処理
    counter = Counter(data)
    values, counts = zip(*counter.most_common())
    
    # 最頻値の特定
    max_count = max(counts)
    modes = [val for val, count in counter.items() if count == max_count]
    
    # 1. 棒グラフ
    ax1 = axes[0, 0]
    bars = ax1.bar(range(len(values)), counts)
    ax1.set_title('頻度分布')
    ax1.set_xlabel('値')
    ax1.set_ylabel('出現回数')
    ax1.set_xticks(range(len(values)))
    ax1.set_xticklabels(values, rotation=45)
    
    # 最頻値を強調
    for i, (val, count) in enumerate(zip(values, counts)):
        if val in modes:
            bars[i].set_color('red')
            ax1.annotate(f'最頻値\n({count}回)', 
                        xy=(i, count), 
                        xytext=(i, count + max_count * 0.1),
                        ha='center', fontsize=10,
                        arrowprops=dict(arrowstyle='->', color='red'))
    
    # 2. 円グラフ
    ax2 = axes[0, 1]
    colors = ['red' if val in modes else 'lightblue' for val in values]
    ax2.pie(counts, labels=values, autopct='%1.1f%%', colors=colors)
    ax2.set_title('割合分布')
    
    # 3. ヒストグラム(数値データの場合)
    ax3 = axes[1, 0]
    if all(isinstance(x, (int, float)) for x in data):
        ax3.hist(data, bins=min(20, len(set(data))), alpha=0.7, edgecolor='black')
        ax3.axvline(modes[0] if modes else 0, color='red', linestyle='--', 
                   label=f'最頻値: {modes[0] if modes else "なし"}')
        ax3.set_title('ヒストグラム')
        ax3.set_xlabel('値')
        ax3.set_ylabel('頻度')
        ax3.legend()
    else:
        ax3.text(0.5, 0.5, 'ヒストグラムは\n数値データのみ', 
                ha='center', va='center', transform=ax3.transAxes)
        ax3.set_title('ヒストグラム(数値データなし)')
    
    # 4. 累積分布
    ax4 = axes[1, 1]
    sorted_counts = sorted(counts, reverse=True)
    cumulative = [sum(sorted_counts[:i+1]) for i in range(len(sorted_counts))]
    ax4.plot(range(1, len(cumulative) + 1), cumulative, 'o-')
    ax4.set_title('累積頻度')
    ax4.set_xlabel('順位')
    ax4.set_ylabel('累積頻度')
    ax4.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    # 統計サマリーを表示
    print("=== 統計サマリー ===")
    print(f"総データ数: {len(data)}")
    print(f"ユニーク値数: {len(counter)}")
    print(f"最頻値: {modes}")
    print(f"最頻値の出現回数: {max_count}")
    print(f"最頻値の割合: {max_count / len(data) * 100:.1f}%")

# 使用例
sample_data = [1, 2, 2, 3, 3, 3, 4, 4, 5, 6, 3, 3, 2, 1]
visualize_mode_analysis(sample_data, "サンプルデータの最頻値分析")

詳細レポートの生成

def generate_mode_report(data, title="最頻値分析レポート"):
    """包括的な最頻値分析レポートを生成"""
    
    from collections import Counter
    import statistics
    
    print("=" * 60)
    print(f"  {title}")
    print("=" * 60)
    
    # 基本統計
    print("\n【基本統計】")
    print(f"総データ数: {len(data):,}")
    print(f"ユニーク値数: {len(set(data)):,}")
    
    if all(isinstance(x, (int, float)) for x in data if x is not None):
        # 数値データの場合
        clean_data = [x for x in data if x is not None and not (isinstance(x, float) and np.isnan(x))]
        if clean_data:
            print(f"平均値: {statistics.mean(clean_data):.2f}")
            print(f"中央値: {statistics.median(clean_data):.2f}")
            print(f"最小値: {min(clean_data)}")
            print(f"最大値: {max(clean_data)}")
            print(f"標準偏差: {statistics.stdev(clean_data):.2f}" if len(clean_data) > 1 else "標準偏差: N/A")
    
    # 最頻値分析
    print("\n【最頻値分析】")
    counter = Counter(data)
    
    if not counter:
        print("データが空のため分析できません")
        return
    
    max_count = max(counter.values())
    modes = [val for val, count in counter.items() if count == max_count]
    
    print(f"最頻値: {modes}")
    print(f"出現回数: {max_count}")
    print(f"全体に占める割合: {max_count / len(data) * 100:.1f}%")
    
    if len(modes) == 1:
        print("モード: 単峰性(unimodal)")
    elif len(modes) == 2:
        print("モード: 双峰性(bimodal)")
    else:
        print(f"モード: 多峰性(multimodal, {len(modes)}個の最頻値)")
    
    # 頻度分布
    print("\n【頻度分布(上位10位)】")
    for rank, (value, count) in enumerate(counter.most_common(10), 1):
        percentage = count / len(data) * 100
        is_mode = "★" if value in modes else " "
        print(f"{rank:2d}. {is_mode} {value}: {count:,}回 ({percentage:.1f}%)")
    
    # 分布の特徴
    print("\n【分布の特徴】")
    unique_counts = list(counter.values())
    
    if len(set(unique_counts)) == 1:
        print("- すべての値が同じ頻度で出現(一様分布)")
    elif max_count == 1:
        print("- 最頻値なし(すべての値が1回ずつ出現)")
    else:
        # 集中度の計算
        total_in_top_3 = sum([count for _, count in counter.most_common(3)])
        concentration = total_in_top_3 / len(data) * 100
        print(f"- 上位3値の集中度: {concentration:.1f}%")
        
        if concentration > 80:
            print("- 高度に集中した分布")
        elif concentration > 50:
            print("- 中程度に集中した分布")
        else:
            print("- 分散した分布")
    
    # データ品質
    print("\n【データ品質】")
    null_count = data.count(None) if None in data else 0
    if hasattr(data, 'isna'):
        null_count += data.isna().sum()
    
    print(f"欠損値: {null_count}個 ({null_count / len(data) * 100:.1f}%)")
    
    if isinstance(data, list):
        duplicate_count = len(data) - len(set(data))
        print(f"重複値: {duplicate_count}個")
    
    print("\n" + "=" * 60)

# 使用例
sample_report_data = [1, 2, 2, 3, 3, 3, 4, 4, 5, 3, 3, 2, 1, None, 3]
generate_mode_report(sample_report_data, "サンプルデータ分析レポート")

まとめ

Pythonで最頻値(mode)を求める方法について、包括的に解説しました。

重要なポイント

  1. 標準ライブラリ: statistics.mode()statistics.multimode()
  2. Counter活用: 詳細な頻度分析と柔軟な処理
  3. pandas統合: DataFrame との親和性
  4. エラーハンドリング: 安全で堅牢な処理

方法別の特徴と使い分け

方法適用場面メリット注意点
statistics.mode()シンプルな最頻値取得高速、標準ライブラリ複数最頻値でエラー
statistics.multimode()複数最頻値対応標準ライブラリ、安全Python 3.8以降
collections.Counter詳細な頻度分析柔軟、多機能やや複雑
pandas.Series.mode()データフレーム統合pandas との親和性外部ライブラリ必要

実用的な活用場面

  • アンケート分析での最多回答の特定
  • ログ分析での最多アクセス時間の発見
  • 品質管理での最多故障パターンの把握
  • マーケティングでの人気商品カテゴリ分析

ベストプラクティス

  • データ型に応じた適切な前処理
  • 複数最頻値への対応
  • エラーハンドリングの実装
  • 可視化による結果の確認

コメント

タイトルとURLをコピーしました