Golangで馬柱用、単勝シェアの標準偏差を求めてみた

Golang

競馬の馬柱に、それぞれの馬の単勝支持率を表示しているのですが、ついでにそのレースでの単勝支持率の標準偏差を求めて、レース検討の参考にしているのですが、

私は単勝派なので、2,3番人気から5,6番人気までを買うことが多く、

上位人気馬から半数の馬で標準偏差を出してみると良いのではないかと思い、この条件で集計してみることにしました。

Golang、久々

サーバサイドをGolangで書いて馬柱を表示しているので、この表示のついでに標準偏差を求めて出力してみることにしました。

久々だったので色々忘れていたので、思い出しつつ・調べつつ書きました。

スライスを馬の人気でソート

ここはChatGPTに質問してサンプルを作ってもらいました。助かりますね〜!

// インデックスを値に基づいてソート
sort.Slice(horsesData, func(i, j int) bool {
	return horsesData[i].Pops < horsesData[j].Pops
})

インデックスを元にソートするサンプルだったので、私は人気で小さい順にソートする用に変更しました。ここまで来たら後は力技、

  • ソートしたスライスを「出走頭数÷2」で出た数値まで取得
  • ピックアップした馬の単勝シェアの標準偏差をループで回して求める

今回の学び

  • math.Ceil() 小数の切り上げ
  • ソートの所の無名関数の使い方

Golangはとりあえず書ける・・・程度のレベルなので、もう少しスキルを上げたいなと思っている今日この頃です。

全コード

package main

import (
	"fmt"
	"math"
	"net/http"
	"sort"

	"github.com/gin-gonic/gin"
)

type HorseDataType struct {
	Index  int
	RaceId string
	Odds   float64  // 単勝支持率
	Pops   int // 人気
}

func MySort(c *gin.Context) {
	// サンプルデータの投入
	horsesData := []HorseDataType{
		{0, "2023082701020601", 0.46, 9},
		{1, "2023082701020601", 15.69, 3},
		{2, "2023082701020601", 36.36, 1},
		{3, "2023082701020601", 2.90, 7},
		{4, "2023082701020601", 3.69, 6},
		{5, "2023082701020601", 6.61, 5},
		{6, "2023082701020601", 18.18, 2},
		{7, "2023082701020601", 0.53, 8},
		{8, "2023082701020601", 11.27, 4},
	}
	// horsesData := []HorseDataType{
	// 	{12, "2023082701020602", 11.94, 2},
	// 	{14, "2023082701020602", 2.80, 6},
	// 	{15, "2023082701020602", 61.54, 1},
	// 	{16, "2023082701020602", 6.84, 4},
	// 	{17, "2023082701020602", 2.05, 5},
	// 	{20, "2023082701020602", 1.19, 7},
	// 	{21, "2023082701020602", 8.33, 3},
	// }
	// fmt.Println(horsesData)

	// 上位馬半数を取得
	_horsesData := PickupUpperHorses(horsesData)
	// ピックアップした(対象の)頭数
	dataSize := len(_horsesData)
	fmt.Printf("[1レースの上位馬のオッズ] 集計頭数: %d\n", dataSize)

	// 上位馬半数の「単勝シェアの標準偏差」を求める
	stdev := CalcStdev(_horsesData)

	c.IndentedJSON(http.StatusOK, gin.H{"標準偏差": stdev, "集計頭数": dataSize})
}

func PickupUpperHorses(horsesData []HorseDataType) []HorseDataType {
	// 人気で昇順ソート後のスライスを渡す
	// インデックスを値に基づいてソート
	sort.Slice(horsesData, func(i, j int) bool {
		return horsesData[i].Pops < horsesData[j].Pops
	})
	// 1レースの頭数
	_member_count := len(horsesData)
	// 集計する対象の頭数
	dataSize := math.Ceil(float64(_member_count) / 2.0)

	return horsesData[:int(dataSize)]
}

func CalcStdev(horsesData []HorseDataType) float64 {
	var oddsSum float64

	// 集計する対象の頭数
	dataSize := len(horsesData)
	// 集計対象とする馬たちのオッズを取得する
	for i := 0; i < int(dataSize); i++ {
		oddsSum += horsesData[i].Odds
	}
	// オッズの平均
	oddsMu := oddsSum / float64(dataSize)
	// オッズの分散
	var muDiffTotal float64
	for i := 0; i < int(dataSize); i++ {
		_mu_diff := horsesData[i].Odds - oddsMu
		muDiffTotal += math.Pow(_mu_diff, 2)
	}
	oddsVariance := muDiffTotal / float64(dataSize)
	// オッズの標準偏差
	oddsStdev := math.Pow(oddsVariance, 0.5)

	fmt.Printf("  合計: %g, 平均: %g, 二乗誤差計: %g, 分散: %g, 標準偏差: %g\n",
		oddsSum, oddsMu, muDiffTotal, oddsVariance, oddsStdev)

	return oddsStdev
}

Pythonでも単勝シェアの標準偏差を求めてみた

やってる内容は標準偏差を求めていること以外は全く別物なのですが、Pythonでも書いてみました。
今回のような計算はやっぱりPythonの方が慣れてる分、楽だったかな・・・と思いました。

from statistics import pstdev  # 母集団の標準偏差、 stdev 標本標準偏差
import math

df_mypred[['race_id', '頭数', '単勝支持率', '人気']]
race_group = df_mypred.groupby('race_id')
for name, race in race_group:
    print(f'- {name} -')
    try:
        # half = round(race.iloc[0]['頭数'] / 2, 1) + 0.5  # 小数でも良いか
        half= math.ceil(race.iloc[0]['頭数'] / 2)  # 切り上げ
    except KeyError as e:
        print(e)
        continue
    # print(half)
    # print(race.loc[0, ['race_id', '頭数', '単勝支持率', '人気']])
    # print(race[['単勝支持率', '人気']])
    upper_horse = race.query("人気<=@half")
    print(upper_horse[['race_id', '頭数', '単勝支持率', '人気']])
    n = upper_horse.shape[0]
    std = pstdev(upper_horse['単勝支持率'])
    print(f"単勝シェアの標準偏差 {round(std, 2)} 上位{int(half)}頭\n")

以上になります。またお会いしましょう

鹿児島県の出水市という所に住んでいまして、インターネット周辺で色々活動して行きたいと思ってるところです。 Webサイト作ったり、サーバ設定したり、プログラムしたりしている、釣りと木工好きなMacユーザです。 今はデータサイエンスに興味を持って競馬AI予想を頑張ってます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


The reCAPTCHA verification period has expired. Please reload the page.

コメントする

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください