総合順位が個々の最高順位よりも上となる確率

たとえばトライアスロン3種目で、総合順位が個々の種目のどの順位よりも上になることがある。
これはちょっと意外に思えるので、問題をうんと単純化して2種目で考えてみよう。

赤、青、黄の3人の、数学と英語のテスト結果が上の図のようだったとすると、
青は数学でも2位、英語でも2位でありながら総合順位は1位だ。
なので、(総合順位) > (個々の種目の最高順位) はあり得ることなのだ。

しかし下の図のような状況だと、青が総合1位となるには、英語、数学のどちらかで1位を取らないといけない。

なので、総合順位が上になるかどうかは、周囲の状況で変わってくる問題だったのだ。

さて、トライアスロン3種目の場合は、2種目で線で描いた状況を、立体化して面で描けば良いわけだ。

赤と黄に足を引っ張る不得意種目があったなら、平均的な青が総合1位なることだってあり得る。

では、総合順位が個々の最高順位よりも上となる人は、全体のうちどれくらい居るのだろうか。
ちょっと考えても分からなかったので、パソコンでシミュレーションを行った結果がこれ。

2科目の場合、全体の 1/3 が、
3種目の場合、全体の 1/4 が、
N種類の場合、全体の 1/(N+1) が、
個別の順位よりも総合順位の方が上
という、シンプルな結果となった。
この結果からすると、
 ・種目数が少なければ、突出せずとも満遍なくこなす人が上位に行くことがある。
 ・種目数が多くなるにつれて、どの分野でも負けないだけでは不十分で、突出した得意分野が望まれる。

以上、シミュレーションの結果は間違い無いだろうと思っているが、
ではなぜ全体の 1/(N+1) となるのか、きちんとした証明ができていない。
2科目、3種目の図を見ると、なんとなく分かるような気もするのだが、うまく説明できない。
誰か賢い人、考えてみて。


# -*- coding: utf-8 -*-
"""
総合力ってどのくらいあるの?
たとえばトライアスロン3種目で、総合順位が個々の種目の最高順位よりも上となる確率は?
"""
import numpy as np
from statistics import mean

class GeneOrder:

    N_MEMBER = 5000    # 参加人数
    
    # 競技の数を2〜15まで変えて試してみる
    def run(self):
        for n_subj in range( 2, 16 ):   # 競技の数を変えてみる
            results = []
            for repeat in range( 10 ):  # 10回繰り返して平均をとる
                ret = self.each_run( n_subj )
                results.append( ret )
            
            # print( results )
            val  = mean(results)
            pred = 1/(n_subj+1)     # 結果はおそらく1/(競技数+1) になると予想
            # 競技数, 実験値, 予想値, 食い違い
            print( "{}, {:.5f}, {:.5f}, {:.05f}".format( n_subj, val, pred, val-pred ) )
    
    # 個々の試行
    # n_sub: 競技の数
    def each_run(self, n_subj):
        
        points  = []    # 各種目ごとの得点配列
        orders  = []    # 各種目ごとの順位配列
        
        # 各種目について得点を付ける
        for i in range( n_subj ):
            # いろんな分布で試してみよう
            x_arr = np.random.rand( self.N_MEMBER )  # N個の一様乱数
            # x_arr = np.random.normal( 0, 1, self.N_MEMBER ) # N個の正規分布 (平均, 分散, 出力数)
            # x_arr = np.random.beta( 4, 2, self.N_MEMBER )   # β分布、非対称でやってみよう
                # 順位についての話なので、分布形状は関係ないようだ。
            
            points.append(x_arr)
        
        # 各種目について順位を付ける
        for i in range( n_subj ):
            x_arr = points[i]
            n_order = x_arr.argsort()   # 得点に対する順位を得る
            orders.append( n_order )
        
        # 総合得点を付ける
        total_points = np.zeros( self.N_MEMBER )
        for m in range( self.N_MEMBER ):
            sum = 0
            for i in range( n_subj ):
                sum += points[i][m]
            total_points[m] = sum
        
        # 総合順位を付ける
        n_arr = np.array(total_points)
        total_order = n_arr.argsort()
        
        # Min(個別順位)を得る
        total_min = np.zeros( self.N_MEMBER )
        for m in range( self.N_MEMBER ):
            min_order = self.N_MEMBER + 1   # 最小の順位を得る
            for i in range( n_subj ):
                if orders[i][m] < min_order:
                    min_order = orders[i][m]
            total_min[m] = min_order
        
        # 出力してみよう
        """
        for m in range( self.N_MEMBER ):
            row = []
            for i in range( n_subj ):
                row.append( points[i][m] )
            for i in range( n_subj ):
                row.append( orders[i][m] )
            row.append( total_points[m] )
            row.append( total_order[m] )
            row.append( total_min[m] )
            
            print( ",".join( map(str, row) ) )
        """
        
        # 総合順位 < Min(個別順位)をカウント
        cnt = 0
        for m in range( self.N_MEMBER ):
            if total_order[m] < total_min[m]:
                cnt += 1
        
        # 結果を返す
        ratio = cnt/self.N_MEMBER
        # print( "{}, {}, {}".format( n_subj, cnt, ratio ) )
        return ratio

if __name__ == '__main__':
    me = GeneOrder()
    me.run()