更に2023年のOssan.fmを総まとめ

良いお年をと言ったな、あれは嘘だ。ということで後編です。

前回でいい感じにデータが貯まったので、もうちょい遊んでみようと思ったのであった。

yohfee.hatenadiary.org

ちょうど.NET InteractiveをF#で遊んでみたかったんだよな。

前回ので形態素解析済みのデータはあるけど、.NETでもKuromojiでできるとのことなんでついでに。 Plotly.NETではいろんなチャートを出せるようだけど、結局今回はテーブルしか使わなかった。

#r "nuget: Lucene.Net.Analysis.Kuromoji, 4.8.0-beta00016"
#r "nuget: Plotly.NET"
#r "nuget: Plotly.NET.Interactive"

open System.IO
open Lucene.Net.Analysis
open Lucene.Net.Analysis.Ja
open Lucene.Net.Analysis.Ja.Dict
open Lucene.Net.Analysis.Ja.TokenAttributes
open Lucene.Net.Analysis.TokenAttributes
open Plotly.NET
open Plotly.NET.StyleParam

形態素解析した結果を使いそうなとこだけ雑に型にして、適当に品詞を判定するプロパティなどを生やしておく。 最初はモジュール関数にしようと思ったけど、F# 8でScalaっぽくアンダースコアを使って書けるようになったのをやりたかった。

type Token =
    { // 語
      term: string
      // 品詞など
      partOfSpeech: string array
      // 原形
      baseForm: string option }

    member private this.partOf part =
        Array.exists ((=) part) this.partOfSpeech

    member this.isSuffix = this.partOf "接尾"
    member this.isNonSelfReliance = this.partOf "非自立"
    member this.isPronoun = this.partOf "代名詞"

    // 有意でなさそうなものを除きたい
    member this.isSignificant =
        not this.isSuffix && not this.isNonSelfReliance && not this.isPronoun

    member this.isNoun = this.partOf "名詞" && this.isSignificant
    member this.isProperNoun = this.partOf "固有名詞" && this.isSignificant
    member this.isCustomNoun = this.partOf "カスタム名詞" && this.isSignificant
    member this.isPerson = this.partOf "人名" && this.isSignificant
    member this.isArea = this.partOf "地域" && this.isSignificant
    member this.isAdjective = this.partOf "形容詞" && not this.isNonSelfReliance

    // 動詞や形容詞などは原形が欲しいときもある
    member this.baseFormOrTerm = Option.defaultValue this.term this.baseForm

カスタム辞書と、データをロードする関数。 まぁドキュメントから察してこんな感じで。

let createDic dic =
    use stream = new StreamReader(path = dic)
    UserDictionary(stream)

let loadData dir dic =
    Directory.GetFiles(dir)
    |> Array.collect (fun file ->
        use reader = new StreamReader(file)

        use tokenizer =
            new JapaneseTokenizer(reader, dic, true, JapaneseTokenizerMode.NORMAL)

        let tsc = TokenStreamComponents(tokenizer)
        use ts = tsc.TokenStream
        ts.Reset()

        seq {
            while ts.IncrementToken() do
                let term = ts.GetAttribute<ICharTermAttribute>()
                let partOfSpeech = ts.AddAttribute<IPartOfSpeechAttribute>()
                let baseForm = ts.AddAttribute<IBaseFormAttribute>()

                yield
                    { term = term.ToString()
                      partOfSpeech = partOfSpeech.GetPartOfSpeech().Split("-")
                      baseForm = baseForm.GetBaseForm() |> Option.ofObj }
        }
        |> Array.ofSeq)

カスタム辞書はお試し用として一件だけ。

おっさんFM,おっさんFM,オッサンエフエム,名詞-カスタム名詞

今回は語の使用回数を表にしてどんな一年だったか振り返りましょうか。

let renderRanking values =
  Chart.Table(
      headerValues = [ "Rank"; "Term"; "Count" ],
      cellsValues = values,
      CellsMultiAlign = [ StyleParam.HorizontalAlign.Center; StyleParam.HorizontalAlign.Left; StyleParam.HorizontalAlign.Right ]
  )

そんじゃロードして。イクゾー!

let data = createDic "dict.csv" |> loadData "data"

(注1:今回も前回同様に解析の精度はお察しなので、あくまでネタとしてお楽しみください。)

(注2:.NET Interactiveの出力では結果を掲載し辛かったので、同じ内容をFsSpectreでコンソールに出力した結果を貼ります。)

では今年のエピソードを通して最も登場した名詞トップ50は・・・

data
|> Array.filter _.isNoun
|> Array.countBy id
|> Array.sortByDescending snd
|> Array.take 50
|> Array.mapi (fun i (t, c) -> [ (i + 1).ToString(); t.term; c.ToString() ])
|> Array.rev
|> renderRanking
┌──────┬────────────────┬───────┐
│ Rank │ Term           │ Count │
├──────┼────────────────┼───────┤
│ 50   │ 一番           │ 167   │
│ 49   │ 4              │ 172   │
│ 48   │ 動画           │ 175   │
│ 47   │ 紹介           │ 176   │
│ 46   │ 二             │ 177   │
│ 45   │ 10             │ 180   │
│ 44   │ 割             │ 184   │
│ 43   │ 辺             │ 186   │
│ 42   │ めちゃめちゃ   │ 190   │
│ 41   │ 逆             │ 190   │
│ 40   │ 5              │ 191   │
│ 39   │ 昔             │ 193   │
│ 38   │ ゲスト         │ 196   │
│ 37   │ 店             │ 202   │
│ 36   │ 家             │ 207   │
│ 35   │ 普通           │ 215   │
│ 34   │ 気持ち         │ 216   │
│ 33   │ 一緒           │ 222   │
│ 32   │ ギター         │ 226   │
│ 31   │ 子供           │ 241   │
│ 30   │ 月             │ 241   │
│ 29   │ 何             │ 251   │
│ 28   │ お願い         │ 253   │
│ 27   │ ポッドキャスト │ 260   │
│ 26   │ 最初           │ 263   │
│ 25   │ 好き           │ 264   │
│ 24   │ 京都           │ 264   │
│ 23   │ 風             │ 269   │
│ 22   │ ー             │ 276   │
│ 21   │ おっさんFM     │ 281   │
│ 20   │ 仕事           │ 287   │
│ 19   │ 映画           │ 315   │
│ 18   │ あと           │ 346   │
│ 17   │ 気             │ 354   │
│ 16   │ クリス         │ 365   │
│ 15   │ 3              │ 397   │
│ 14   │ 時間           │ 410   │
│ 13   │ 最近           │ 424   │
│ 12   │ 一             │ 481   │
│ 11   │ 前             │ 528   │
│ 10   │ 長山           │ 605   │
│ 9    │ 2              │ 670   │
│ 8    │ 今             │ 717   │
│ 7    │ 1              │ 738   │
│ 6    │ 樋口           │ 742   │
│ 5    │ 確か           │ 769   │
│ 4    │ 自分           │ 775   │
│ 3    │ 話             │ 836   │
│ 2    │ 人             │ 1162  │
│ 1    │ 感じ           │ 2208  │
└──────┴────────────────┴───────┘

前回の結果からそんな気はしてたって感じ。 とはいえ傾向的にはそれっぽい結果に見えなくもない。 樋口ってそんな出てたっけ?ていうか何。。。

人名に限ると・・・

data
|> Array.filter _.isPerson
|> Array.countBy id
|> Array.sortByDescending snd
|> Array.take 20
|> Array.mapi (fun i (t, c) -> [ (i + 1).ToString(); t.term; c.ToString() ])
|> Array.rev
|> renderRanking
┌──────┬────────────┬───────┐
│ Rank │ Term       │ Count │
├──────┼────────────┼───────┤
│ 20   │ 健         │ 15    │
│ 19   │ かおり     │ 15    │
│ 18   │ かね       │ 17    │
│ 17   │ エル       │ 20    │
│ 16   │ ジャクソン │ 20    │
│ 15   │ 宮川       │ 21    │
│ 14   │ 中山       │ 30    │
│ 13   │ 近藤       │ 35    │
│ 12   │ 竹志       │ 38    │
│ 11   │ 池田       │ 38    │
│ 10   │ 二宮       │ 39    │
│ 9    │ 永山       │ 43    │
│ 8    │ 山本       │ 46    │
│ 7    │ じゅん     │ 48    │
│ 6    │ 橋本       │ 51    │
│ 5    │ 香里       │ 57    │
│ 4    │ 笑         │ 59    │
│ 3    │ ハテナ     │ 84    │
│ 2    │ クリス     │ 365   │
│ 1    │ 長山       │ 605   │
└──────┴────────────┴───────┘

長山さんが堂々の一位ですね。 この辞書だとハテナが人名になるのかー。 エルとジャクソンが存在感を放っているけどサミュエルどこ行ったwww

そして地名だと・・・

data
|> Array.filter _.isArea
|> Array.countBy id
|> Array.sortByDescending snd
|> Array.take 20
|> Array.mapi (fun i (t, c) -> [ (i + 1).ToString(); t.term; c.ToString() ])
|> Array.rev
|> renderRanking
┌──────┬──────────────┬───────┐
│ Rank │ Term         │ Count │
├──────┼──────────────┼───────┤
│ 20   │ 関西         │ 21    │
│ 19   │ 那須塩原     │ 21    │
│ 18   │ チリ         │ 22    │
│ 17   │ 博多         │ 23    │
│ 16   │ アルゼンチン │ 23    │
│ 15   │ 倉敷         │ 23    │
│ 14   │ 大阪         │ 25    │
│ 13   │ 南米         │ 25    │
│ 12   │ 九州         │ 32    │
│ 11   │ 英           │ 35    │
│ 10   │ 深井         │ 37    │
│ 9    │ 松本         │ 55    │
│ 8    │ アメリカ     │ 60    │
│ 7    │ 岡山         │ 88    │
│ 6    │ 鹿児島       │ 89    │
│ 5    │ 福岡         │ 123   │
│ 4    │ 日本         │ 129   │
│ 3    │ 東京         │ 142   │
│ 2    │ 京都         │ 264   │
│ 1    │ 樋口         │ 742   │
└──────┴──────────────┴───────┘

樋口は地名だったのか。 Wikipediaによるとあちこちにあるし、京都にもあるみたいだしなー。 って絶対そんなことないだろって思ってWisperの書き起こしを確認してみた。

(240. 相談したいことがあるんです (ゲスト:june29さん))
毎週月曜日の朝に
一家総出で大和田朝会をやる
っていうやつで
樋口 一回仕事お二人ですよね
深井 そうです
一家総出でこの1週間どうだったかと
次の1週間何やるかを
お互い書いて
それについて喋って
じゃあこの日はこうした方がいいね
みたいなちょっとスケジュールとかを
調整したりするっていうのを始めて
それによって
曜日間隔も全て失って
ただ漂う生き物にならないように
まず月曜日の朝っていうのは
意識づけようと思って
これをまず入れましたね
こういうのないと怖い気がしたので
樋口 確かに確かに
深井 ちなみに奥様は
お仕事されてる
樋口 そうですねお仕事
会社員はやってないので
2人とも無所属なんですけども
ちょいちょいなんか
樋口 無所属で
深井 無所属で受けてやったりとか
自分の好きなものを作って
発表したりみたいな感じですね

これはダメな奴でしたね。 深井お前もか。 というわけでやっぱ京都が一位! 上位以外はゲスト回の影響をモロに受けてるのが面白い。

続いて食べ物を・・・と思ったけど分類がないので人力検索になっちゃうので、余力があるときに。 とんかつでは?と思ったけど52回だったので怪しいな。 カツだけとかかつ丼とかに分散してるのもありそう。 誰か当ててみてください、コメントいただければ調べます。

気を取り直して形容詞では・・・

data
|> Array.filter _.isAdjective
|> Array.countBy _.baseFormOrTerm
|> Array.sortByDescending snd
|> Array.take 20
|> Array.mapi (fun i (t, c) -> [ (i + 1).ToString(); t; c.ToString() ])
|> Array.rev
|> renderRanking
┌──────┬──────────┬───────┐
│ Rank │ Term     │ Count │
├──────┼──────────┼───────┤
│ 20   │ 嬉しい   │ 96    │
│ 19   │ 安い     │ 98    │
│ 18   │ うまい   │ 106   │
│ 17   │ 怖い     │ 106   │
│ 16   │ 新しい   │ 108   │
│ 15   │ 大きい   │ 112   │
│ 14   │ っぽい   │ 115   │
│ 13   │ 早い     │ 116   │
│ 12   │ 悪い     │ 134   │
│ 11   │ 美味しい │ 139   │
│ 10   │ よい     │ 177   │
│ 9    │ 難しい   │ 192   │
│ 8    │ 高い     │ 201   │
│ 7    │ 多い     │ 269   │
│ 6    │ 良い     │ 280   │
│ 5    │ 楽しい   │ 320   │
│ 4    │ 面白い   │ 662   │
│ 3    │ すごい   │ 1092  │
│ 2    │ ない     │ 1240  │
│ 1    │ いい     │ 1414  │
└──────┴──────────┴───────┘

いいですねー。すごい!面白い!楽しい!良い!最高!!!!

いかがでしたか? 誰か真面目にガチでやってください。

そういえばOssan.fmパーカーも素敵ですね。

それでは地獄の年越しサーモンラン祭りでお会いしましょう。