GolangとGoogle Chartを使って、ツイート感情値を時系列にグラフ化してみよう

2021-05-16

はじめに

転生したらスライムだった件を放送済みの話を全話見ました。 あれ、良いですね。スライム癒やされますね。作者は多分ぷるんぷるんなものが好きなんですかね。 Amazon Prime Videoでめぼしいアニメは一通り見た気がします。 何かオススメがあれば教えて下さい。 ドラマでも映画でも、小説でも、アニメでもお待ちしております。 娯楽に飢えております。

今回やること

Golangで取り込んだCSVファイルをGoogle Chartで時系列に表示します。

せっかく表示するならと、感情値を時系列に出力したものが見たいとコメントでリクエストがありましたのでそちらを実装してみようと思います。 (ぽんすけさん、いつもコメントありがとうございます。励みになります。)

今回は余裕だなと思ってはじめましたが、Google Chartの所でハマりました。とても勉強になりました。

やっぱビジュアル的に見やすいのはいいですね。ひと目で分かるのがいいです。

CSVファイルは、前回記事Twitterの過去ツイートからベストポジティブツイートを見つけよう(golang)で作成したものを使います。

前回記事の出力のままでは、余計な情報も有ります。

  • 冒頭のデータテーブル
  • ファイルの中盤に区切りとして導入した「——————————」より下を全部削除 以上2点だけ修正して利用してください。

実行コード

事前準備

go get "github.com/jszwec/csvutil"

csvutilを使います。

main.go

コード全文

package main

import (
    "fmt"
    "html/template"
    "io/ioutil"
    "net/http"
    "sort"

    "github.com/jszwec/csvutil"
)

var templates = template.Must(template.ParseFiles("index.html"))

type Twitter struct {
    Create  string  `csv:"create"`
    Tweet   string  `csv:"tweet"`
    Sum     float64 `csv:"sum"`
    Average float64 `csv:"average"`
}

type TweetdataList []Twitter

var Tweetdatas TweetdataList

func (e TweetdataList) Len() int {
    return len(e)
}

func (e TweetdataList) Less(i, j int) bool {
    return e[i].Create < e[j].Create
}

func (e TweetdataList) Swap(i, j int) {
    e[i], e[j] = e[j], e[i]
}

func readCSV() TweetdataList {
    fp, err := ioutil.ReadFile("graph.csv")
    if err != nil {
        panic(err)
    }
    _ = csvutil.Unmarshal(fp, &Tweetdatas)

    sort.Sort(TweetdataList(Tweetdatas))
    return Tweetdatas
}

func viewCharthandler(w http.ResponseWriter, r *http.Request) {
    err := templates.ExecuteTemplate(w, "index.html", Tweetdatas)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

func StartWebServer() error {
    http.HandleFunc("/chart/", viewCharthandler)
    return http.ListenAndServe(fmt.Sprintf(":%d", 8080), nil)
}

func main() {
    _ = readCSV()

    StartWebServer()
}

CSVファイルデータの構造体格納

type Twitter struct {
    Create  string  `csv:"create"`
    Tweet   string  `csv:"tweet"`
    Sum     float64 `csv:"sum"`
    Average float64 `csv:"average"`
}

type TweetdataList []Twitter

var Tweetdatas TweetdataList

func readCSV() TweetdataList {
    fp, err := ioutil.ReadFile("graph.csv")
    if err != nil {
        panic(err)
    }
    _ = csvutil.Unmarshal(fp, &Tweetdatas)

    sort.Sort(TweetdataList(Tweetdatas))
    return Tweetdatas
}

readCSVのReadFile()でCSVファイルを取り込みます。 csvutil.Unmarshalを使うことで、データの中身を構造体に詰め込むことができます。

データ構造の中身をSortする

前回記事では、感情値で並び替えを行っていたのですが、時系列に並べかえます。 当然、もともと時系列で並べて出力して於けば不要な処理ですが、再実行するのも時間がかかるため今回はSortで対応します。

func (e TweetdataList) Len() int {
    return len(e)
}

func (e TweetdataList) Less(i, j int) bool {
    return e[i].Create < e[j].Create
}

func (e TweetdataList) Swap(i, j int) {
    e[i], e[j] = e[j], e[i]
}

Golangに準備されているSortパッケージを活用します。 SortパッケージではInterfaceだけ用意されているため、こうして実体となる関数を定義して使います。 構造体の中身で並び替えを行いたい場合、上記のように記載します。 今回は、日時で並べ替えを行いたいため、 .Createで比較演算した値を返り値として使用します。

呼び出し元はこのような書き方です。

sort.Sort(TweetdataList(Tweetdatas))

これでOKです。

Google Chartへデータ渡しを行う方法

func viewCharthandler(w http.ResponseWriter, r *http.Request) {
    err := templates.ExecuteTemplate(w, "index.html", Tweetdatas)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
    }
}

func StartWebServer() error {
    http.HandleFunc("/chart/", viewCharthandler)
    return http.ListenAndServe(fmt.Sprintf(":%d", 8080), nil)
}

ExecuteTemplate()で読み出すベースとなるhtmlファイルと、渡すデータを指定します。 Tweetdatasを今回は渡しています。 これはツイートデータと日時、感情値が格納された構造体配列です。

index.html

Golangからデータを受け取る部分を書いていきます。 Google ChartのサンプルにあるLine Chartのコードをベースに書いていきます。 修正箇所は、var data ..の箇所です。

コード全文

<html>
<head>
  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">
      google.charts.load('current', {'packages':['line']});
      google.charts.setOnLoadCallback(drawChart);

    function drawChart() {

      var data = google.visualization.arrayToDataTable([
                {{ range. }}
                ['{{.Create}}',0, {{.Sum}}, {{.Average}}],
                {{ end}}
            ], true);

    var options = {
        chart: {
          title: 'aaa',
          subtitle: 'a'
        },
        width: 1920,
        height: 1080,
        axes: {
          x: {
            0: {side: 'top'}
          }
        }
      };

      var chart = new google.charts.Line(document.getElementById('line_top_x'));

      chart.draw(data, google.charts.Line.convertOptions(options));
    }
  </script>
</head>
<body>
  <div id="line_top_x"></div>
</body>
</html>

データ受信部

Sampleコードをこのように変えていきます。 修正箇所は特に作り込みもないのでこの部分だけです。

var data = google.visualization.arrayToDataTable([
                    {{ range. }}
                    ['{{.Create}}',0, {{.Sum}}, {{.Average}}],
                    {{ end}}
            ], true);

var options = {
    chart: {
        title: 'aaa',
        subtitle: 'a'
    },
    width: 1920,
    height: 1080,
    axes: {
        x: {
            0: {side: 'top'}
        }
    }
};
{{range.}}

{{end}}

いわゆるForループみたいなもので、配列を末尾まで取得することができます。 {{.構造体名}}を指定し中身を取り出していきます。 Tweetデータは今回グラフ描画時には不要となるため0埋めで実装しています。

実行結果

ツイート数が多く一括で取得しようとすると処理ができた部分から順次出力されるようです。 まずは、合計値から出力されている様子を示します。 感情グラフ

PCスペックにもよりますが大体アニメ一本分ぐらい待機していると、感情の平均値が出力されます。 黒っぽく見えますがindex.htmlを変更すれば色は変えることができます。

平均値

ちなみに日付に合わせてクリックすると、日時と感情値の合計値が取得できます。

合計値

ツイートデータが多いため全て描画を待っていると時間がかかると思います。 ツイート数を減らすとか、画面描画のタイミングを100ツイート取り込む度になど変えるなどが改善点としては思いつきます。

まとめ

Google Chartでグラフ表示する方法と、Golangから値を渡しグラフ化する方法を学ぶことができました。 Google Chartは多様なグラフが準備されているため、プログラムでデータ収集する人が結果を表示するのには良いツールとなると思います。 何より、Golangから少し手の混んだグラフを表示したい場合の選択肢として有力ではないかと考えています。

どうも負の感情が強く出る傾向にありますが、これは感情値の出し方がイマイチなんだと思います。ここは改善ポイントですね。

今日はここまで、明後日はいよいよ息子の出産日です。 楽しみです。早く逢いたいものです。