Edited at

ナンバーズ予想で学ぶ PowerShell によるデータ分析

More than 3 years have passed since last update.

PowerShell には CSV ファイルを読み込むコマンドや、集計するためのコマンドがあります。そのため、ちょっとした集計作業であれば実行することが可能です。ここでは、ナンバーズ予想を題材に、PowerShell によるデータ分析(と呼べるほど高級な処理はやりませんが……)の方法についてまとめます1

※「ナンバーズ予想で学ぶ ~」と言いつつも、この記事の大半は「予想に至るまでに必要な前処理やデータ俯瞰を PowerShell で行う方法」の説明です。予想の部分だけ読みたい方は「各当せん番号のストレート口数の平均を求める」へ飛んでください!


準備


データのダウンロード

ナンバーズ3の過去データを用意します。

今回は ナンバーズ34 データダウンロードサービス からデータをお借りしました。


PowerShell の基本を習得

この記事では PowerShell の基本的な部分については説明しないので、そもそも PowerShell をよく知らんぞという方は以下のページなどをまずはご覧ください。

また、各コマンドレットの詳細は

Get-Help <コマンドレット名> -Online

で確認できます。オンラインヘルプが開きます。


とりあえずファイルを開いてみる

数行出力して、ファイルの構造を把握します。


コマンド

# 先頭5行を表示する

Get-Content -Path .\Numbers3.csv -Encoding Default -TotalCount 5

# 末尾5行を表示する
Get-Content -Path .\Numbers3.csv -Encoding Default -Tail 5

項目
説明

Get-Content
テキストファイルを1行ずつ読み込む

 -Encoding Defalut

文字コードを指定する。Default は Shift-JIS

 -TotalCount 5

先頭の5行だけ読み込む

 -Tail 5

末尾の5行だけ読み込む


実行結果


先頭5行

"ナンバーズ34","データダウンロードサービス","http://toe.jp/numbers34/"

"回別","抽選日","当せん番号","ストレート口数","ストレート金額","ボックス口数","ボックス金額","セットストレート口数","セットストレート金額","セットボックス口数","セットボックス金額","ミニ口数","ミニ金額"
"第4304回","2015/12/24(木)","187","47","116,000","508","19,300","123","67,600","1,083","9,600","419","11,600"
"第4303回","12/23(水)","447","70","116,400","204","38,800","139","77,600","198","19,400","404","11,600"
"第4302回","12/22(火)","454","83","115,300","198","38,400","133","76,800","263","19,200","423","11,500"


末尾5行

"第5回","11/04(金)","592","0","113,400","0","18,900","0","66,100","0","9,400","0","11,300"

"第4回","10/28(金)","105","0","57,200","0","9,500","0","33,300","0","4,700","0","5,700"
"第3回","10/21(金)","194","0","71,700","0","11,900","0","41,800","0","5,900","0","7,100"
"第2回","10/14(金)","988","0","333,500","0","111,100","0","222,300","0","55,500","0","33,300"
"第1回","10/07(金)","191","0","119,800","0","39,900","0","79,800","0","19,900","0","11,900"

ここから読み取るべき内容としては、


  • 1行目にデータ提供元が記述されている

  • 2行目にヘッダが記述されている

  • 末尾にコメントなどの記述はなく、最後の行までレコードが入っている

  • 文字コードは Shift-JIS

  • 区切り文字はカンマ

あたりでしょうか。また、以下は注意したほうがよさそうです。


  • 年が一部のレコードにしか明記されていない

  • 数値がカンマで桁区切りされている


ファイルの規模を把握する

数万行にも及ぶような巨大なファイルを扱うには PowerShell は向いていないので、先にファイルの規模を把握して PowerShell でさばけそうか確認します。


コマンド

# 行数や文字数を表示する

Get-Content -Path .\Numbers3.csv -Encoding Default |
Measure-Object -Line

# 1レコードだけ読み込み、属性の一覧を得る
Get-Content -Path .\Numbers3.csv -Encoding Default |
Select-Object -Skip 1 -First 2 |
ConvertFrom-Csv

項目
説明

Measure-Object
オブジェクトの統計情報を生成する

 -Line

行数を数える

Select-Object
オブジェクトを(この場合は行を)選択する

 -Skip 1

先頭1行を読み飛ばす

 -First 2

-Skip で読み飛ばした行の後に続く先頭2行を読み込む

ConvertFrom-Csv
CSV 文字列を読み込む


実行結果


行数と文字数

Lines Words Characters Property

----- ----- ---------- --------
4306


属性の一覧

回別         : 第4304回

抽選日 : 2015/12/24(木)
当せん番号 : 187
ストレート口数 : 47
ストレート金額 : 116,000
ボックス口数 : 508
ボックス金額 : 19,300
セットストレート口数 : 123
セットストレート金額 : 67,600
セットボックス口数 : 1,083
セットボックス金額 : 9,600
ミニ口数 : 419
ミニ金額 : 11,600

約4,300行 × 13列 程度の小さな表なので、PowerShell で処理しても問題なさそうです。


不要な行を削除する

1行目のデータ提供元はいらないので削除します。


コマンド

# 削除前を表示する

Get-Content -Path .\Numbers3.csv -Encoding Default -TotalCount 3

# 1行目を削除してファイルに保存する
Get-Content -Path .\Numbers3.csv -Encoding Default |
Select-Object -Skip 1 |
Out-File -FilePath .\Numbers3_without_comments.csv -Encoding Default

# 削除後を表示する
Get-Content -Path .\Numbers3_without_comments.csv -Encoding Default -TotalCount 3

項目
説明

Out-File
テキストファイルに書き込む

 -Encoding Defalut

文字コードを指定する。Default は Shift-JIS


実行結果


Numbers3.csv

"ナンバーズ34","データダウンロードサービス","http://toe.jp/numbers34/"

"回別","抽選日","当せん番号","ストレート口数","ストレート金額","ボックス口数","ボックス金額","セットストレート口数","セットストレート金額","セットボックス口数","セットボックス金額","ミニ口数","ミニ金額"
"第4304回","2015/12/24(木)","187","47","116,000","508","19,300","123","67,600","1,083","9,600","419","11,600"


Numbers3_without_comments.csv

"回別","抽選日","当せん番号","ストレート口数","ストレート金額","ボックス口数","ボックス金額","セットストレート口数","セットストレート金額","セットボックス口数","セットボックス金額","ミニ口数","ミニ金額"

"第4304回","2015/12/24(木)","187","47","116,000","508","19,300","123","67,600","1,083","9,600","419","11,600"
"第4303回","12/23(水)","447","70","116,400","204","38,800","139","77,600","198","19,400","404","11,600"

1行目が削除されていることが確認できます。


CSV ファイルを読み込む

CSV ファイルをオブジェクトとして読み込みます。


コマンド

# CSV ファイルを読み込む

$csv = Import-Csv -Path .\Numbers3_without_comments.csv -Encoding Default

# 表形式で表示する
$csv |
Select-Object -First 10 |
Format-Table

# CSV ファイルの各列には、オブジェクトのプロパティとしてアクセスできる
$csv[0].当せん番号

項目
説明

Import-CSV
CSV ファイルを読み込む

 -Encoding Defalut

文字コードを指定する。Default は Shift-JIS

Format-Table
表形式に整えて出力する

Import-CSV で CSV ファイルを読み込むと、各列をプロパティとして持つオブジェクトの配列として読み込まれます。デフォルトでは CSV ファイルの1行目がヘッダとして解釈され、ヘッダの値がそのままオブジェクトのプロパティ名となります。


実行結果


Format-Table

回別     抽選日           当せん番号 ストレート口数 ストレート金額 ボックス口数 ボックス金額 セットストレート口数 セットストレート金額 セットボックス口数

-- --- ----- ------- ------- ------ ------ ---------- ---------- ---------
第4304回 2015/12/24(木) 187 47 116,000 508 19,300 123 67,600 1,083
第4303回 12/23(水) 447 70 116,400 204 38,800 139 77,600 198
第4302回 12/22(火) 454 83 115,300 198 38,400 133 76,800 263
第4301回 12/21(月) 530 109 105,000 527 17,500 204 61,200 959
第4300回 12/18(金) 966 82 114,900 270 38,300 134 76,600 294
第4299回 12/17(木) 308 152 72,700 462 12,100 317 42,400 950
第4298回 12/16(水) 166 93 105,400 242 35,100 130 70,200 325
第4297回 12/15(火) 484 63 115,800 309 38,600 117 77,200 299
第4296回 12/14(月) 671 83 99,200 583 16,500 223 57,800 1,093
第4295回 12/11(金) 332 104 104,000 233 34,600 126 69,300 255


$csv[0].当せん番号

187


CSV ファイルをオブジェクトとして読み込むことができました。

列幅の自動調整が全角文字に対応していないダサさはありますが……(´・ω・`)


値の埋まり具合を確認する

欠損値が多いと後の統計処理に影響するので、各列の値の埋まり具合を確認します。


コマンド

$csv |

Get-Member -MemberType NoteProperty | %{ $_.Name } | # 列名を取得する
%{ [PSCustomObject]@{ Name = $_; Count = ($csv.$_ | ?{ $_ -ne "" }).Length } }

項目
説明

Get-Member
オブジェクトのメンバー(メソッドやプロパティなど)の一覧を取得する

 -MemberType NoteProperty

メンバーのうち、プロパティだけを取得する

%{}
コレクションの各要素に対して処理を実行する

@{}
連想配列を定義する

?{}
条件に合う要素を抽出する

%{}ForEach-Object のエイリアスで、パイプラインで渡されたコレクションの各要素に対して {} 内に記述された処理を実行します。コレクションの各要素は $_ で参照できます。

?{}Where-Object のエイリアスで、パイプラインで渡されたコレクションの各要素を調べ {} 内に記述された条件に一致する要素のみを返します。%{} と同様、コレクションの各要素は $_ で参照できます。

@{} を使うと連想配列を定義できます。一方で、PowerShell が基本としている連想配列的なオブジェクトは PSCustomObject なので、そちらに合わせてキャストしています。直接 PSCustomObject を作成するほうが処理としては素直ですが、記述が微妙に長くなるのでここでは連想配列を介しています。


連想配列を介さない方法

$csv |

Get-Member -MemberType NoteProperty |
%{ $_.Name } |
%{
$record = New-Object PSCustomObject | Select-Object Name, Count
$record.Name = $_
$record.Count = ($csv.$_ | ?{$_ -ne "" }).Length
$record
}


実行結果

Name       Count

---- -----
ストレート口数 4304
ストレート金額 4304
セットストレート口数 4304
セットストレート金額 4304
セットボックス口数 4304
セットボックス金額 4304
ボックス口数 4304
ボックス金額 4304
ミニ口数 4304
ミニ金額 4304
回別 4304
当せん番号 4304
抽選日 4304

行数がヘッダ含め4,305行だったので、欠損値はないようです。


各当せん番号のストレート口数の平均を求める

ナンバーズは賭け金を当せん者で分け合うような形になっているらしく、当せん金額も購入者や当せん者の人数に応じて変化します。そこで、


  1. 各番号の当せん確率は同じ

  2. 毎回の購入者数は一定

と仮定すると、賭ける人の少ない番号に(当せん者数の少ない番号に)賭ければ、当せんする確率は同じでありながら当せん金額は大きくなるので、期待値が上がるはずです。123 や 777 など誰もが連想しがちな番号が存在することからも、番号によって当せん者数にばらつきがあることは容易に想像できます。

ということで、各当せん番号のストレート口数の平均を求めます。


コマンド

$winner_average =

$csv |
Group-Object -Property 当せん番号 |
%{ [PSCustomObject]@{ 当せん番号 = $_.Name; ストレート口数平均 = $_.Group | %{ $_.ストレート口数 } | Measure-Object -Average | %{ $_.Average } } } |
Sort-Object -Property ストレート口数平均 -Descending

$winner_average |
%{ [PSCustomObject]@{ 当せん番号 = $_.当せん番号; ストレート口数平均 = "{0,5:n1}" -f $_.ストレート口数平均 }} |
Select-Object -First 10

$winner_average |
?{ $_.ストレート口数平均 -ne 0 } |
%{ [PSCustomObject]@{ 当せん番号 = $_.当せん番号; ストレート口数平均 = "{0,5:n1}" -f $_.ストレート口数平均 }} |
Select-Object -Last 10

項目
説明

Group-Object
指定したプロパティの値ごとにグルーピングする

Sort-Object
指定したプロパティの値でソートする


結果


ストレート口数平均上位10件

当せん番号 ストレート口数平均

----- ---------
888 809.0
323 792.0
555 732.0
777 658.7
999 614.8
444 541.5
000 480.5
222 470.7
666 453.6
333 364.0


ストレート口数平均下位10件

当せん番号 ストレート口数平均

----- ---------
167 28.7
107 28.0
144 26.8
284 26.0
450 25.0
085 20.0
075 16.8
277 15.8
882 15.0
292 11.3

想定していた通りゾロ目などわかりやすい番号が上位に来ており、その数は下位の10倍以上にもなります。

このことから、ナンバーズ3を購入する時は下位10位に載っているような番号を選ぶことで、当せん金の期待値を高めることができるかもしれません(`・ω・´)





  1. コマンドレットの記述方針として、エイリアスは極力利用せず、プロパティ名も省略しないようにしています。コマンドレットの使い方を正確に覚えきれていない身としては、エイリアスの利用やプロパティ名の省略はわかりにくくなるので……ISE 使えばコマンドレットが長くても大した手間ではないし