u++の備忘録

自分のTwitter投稿内のURLを分析してみた

これは何?

次の記事に着想を得て、自分のデータでやってみました。具体的には、2015年6月から現在までの自分のTwitter投稿から、全部のURLを抽出し、ドメイン単位で集計しました。

www.buzzfeed.com

データの取得

twilog*1に自分の投稿データが保存されていたので、ダウンロードしました。なおTwitterでは自分の投稿をダウンロードする機能が公式に提供*2されていますが、今年7月のインシデントの影響で現在は使えない状況のようです*3

分析結果

全投稿数は40903でした。Excelで処理するには少し大きいので、手慣れているPythonで分析しました。最終的には、頻度の高い上位40サイトを棒グラフで表示しました。

import collections
import itertools
import re

import pandas as pd


df = pd.read_csv(
    "drive/My Drive/twitter/upura0200827.csv",
    header=None,
    names=["id", "timestamp", "text"]
)

print(df.loc[0, "text"])
"""
初回の題材はVRらしい。シーズン18の最終回はディープフェイクだったし、技術的な話題を盛り込んでるな。

『相棒シーズン19』10月スタート!水谷豊、21年目の“一心同体” 反町隆史も続投(サンケイスポーツ)
#Yahooニュース
https://news.yahoo.co.jp/articles/dfffd7fab8172ba9d8997f54ab2314259ce7c811
"""

pattern = "https?://[\w/:%#\$&\?\(\)~\.=\+\-]+"
urls = re.findall(pattern, ' '.join(list(itertools.chain(df["text"]))))
print(len(urls))    # 12314
domains = [url.split('/')[2] for url in urls]

c = collections.Counter(domains)
res = pd.DataFrame(c.most_common()[:40])
res.columns = ["Domain", "Frequency"]
res.plot.bar(x="Domain", y="Frequency", figsize=(13, 5))

f:id:upura:20200827031251p:plain

考察

上位10件を以下に示します。最も多い pic.twitter.com と 3番目の twitter.com はそれぞれ、画像と引用リツイートだと考えられます。

f:id:upura:20200827031504p:plain

2、4、6番目はニュースサイトでした。headlines.yahoo.co.jpnews.yahoo.co.jp を足すと 942 + 437 = 1379個 で www.nikkei.com の1205個を上回ります。ニュースの情報源として、主に2種類のサイトを見ていることが読み取れます。8番目にも www.asahi.com が来ています。残りは技術的なサイトで、自身の技術ブログ upura.hatenablog.com や Qiita qiita.comarXiv arxiv.orgGitHub github.com が続きました。

おわりに

本記事では、BuzzFeedの記事に倣って自分のTwitter投稿内のURLを分析しました。個人の興味が色濃く出た結果のように感じます。2017年にソフトウェアエンジニアとして就職した後は少し技術的な投稿が比重が増えている認識なので、時系列で分割した分析も面白そうだなと思っています。

Kaggleの「おすすめコンペは何?」への答えを考えた

「Rist主催 Kaggle Workshop #1」にてLT発表しました。テーマは「おすすめコンペは何?」という質問への答え方です。発表資料やプレゼンテーション動画を掲載したので、ご興味あらばご覧いただければと思います。

最近Kaggle系のイベントに飢えていたので、このようなイベントに参加できて楽しかったです。

イベント概要

rist.connpass.com

発表資料

動画

youtu.be

Pandasのパイプラインを作る「pdpipe」を使ってみた

Pandasのパイプラインを作る「pdpipe」というライブラリを知ったので、少し触ってみました。本記事では、簡単な使い方および良かった点・悪かった点をまとめます。

使い方

KaggleのTitanicデータセットで検証しました。一連の処理はNotebookを公開しています。

import pandas as pd


train = pd.read_csv('../input/titanic/train.csv')
test = pd.read_csv('../input/titanic/test.csv')

www.kaggle.com

インストール

公式ドキュメントに記載の通り、pipでインストール可能です。

pdpipe.github.io

pip install pdpipe

パイプラインの構築

パイプラインの記述する方法は、いくつか存在します。ここではリストで列挙する方法を採用しましたが、例えば+演算子で羅列することも可能です。

pdpipe.github.io

import pdpipe as pdp


CATEGORICAL_COL = ['Sex', 'SibSp', 'Embarked']
DROP_COL = ['PassengerId', 'Name', 'Ticket', 'Parch', 'Cabin']

pipeline = pdp.PdPipeline([
    pdp.Encode(CATEGORICAL_COL),
    pdp.ColDrop(DROP_COL)
])

今回は簡単な処理として、カテゴリ変数 CATEGORICAL_COL のラベルエンコーディングと、一部のカラム DROP_COL の削除を実行します。

前処理

「悪かった点」で後述しますが、ラベルエンコーディングの対象となるカラムに欠損値が含まれる場合に、次のエラーが発生します。

TypeError: Encoders require their input to be uniformly strings or numbers. Got ['float', 'str']

この問題に対処すべく、事前にカテゴリ変数の欠損値処理が必要です。「パイプラインで事前に定義しよう・・・」と考えたのですが、現状は欠損値を埋める処理が用意されておらず、独自に対応する必要がありました。

github.com

for c in CATEGORICAL_COL:
    train[c].fillna('<missing>', inplace=True)
    test[c].fillna('<missing>', inplace=True)

パイプラインの実行

構築したパイプラインは、sklearnの要領でtrain, testに適用できます。

train = pipeline.fit_transform(train, verbose=True)
test = pipeline.transform(test)
- Encode Sex, SibSp, Embarked..
100%|██████████| 3/3 [00:00<00:00, 132.06it/s]
- Drop columns PassengerId, Name, Ticket, Parch, Cabin..

before

f:id:upura:20200728042242p:plain

after

f:id:upura:20200728042248p:plain

良かった点

事前にパイプラインを定義することで、処理の見通しは非常に良くなると感じました。trainとtestに同一のパイプラインを適用する形式のため、処理の整合性が取れる利点もあると思います。

悪かった点

まずはカテゴリ変数に欠損値が含まれる場合のラベルエンコーディングでエラーが発生する点および、その回避に別の処理が必要な点が挙げられます。せめてパイプライン内に欠損値処理を盛り込めればなと感じました。issueコメントを見るに、contributionのチャンスかもしれません。

同じくカテゴリ変数のラベルエンコーディングの処理で、testで新たに出現する値に対してエラーが発生するのも辛いところでした。パイプライン内で sklearn.preprocessing.LabelEncoder() をラッピングしている都合上、このエラーを回避する処理を外部から付け加えるのは難しいです。許される場合は、次のようにtrainとtestを結合してから処理するのが現実的な解決策かもしれません。

data = pd.concat([train, test], sort=False)

おわりに

本記事では、Pandasのパイプラインを作るライブラリ「pdpipe」の簡単な使い方および良かった点・悪かった点をまとめました。手軽に利用でき、パイプラインを構築する上での利点は感じられた一方で、現時点の機能では若干の使いづらさを感じたというのが率直な印象です。余裕があれば、PRを出してみようかなと思いました。

streamlitでwebアプリ作成

少し前に話題になっていた「streamlit」を用いて、簡単なwebアプリを作ってみました。デザイン部分をほとんど意識せず、お手軽にwebアプリを作成できます。公式ドキュメントも充実しており、分かりやすかったです。細かいデザインに手を入れる必要がない場合など、サクッとデモを作りたい場合には重宝する仕様になっているなと感じました。

www.streamlit.io

デモ

ソースコード

github.com

「リングフィットアドベンチャー」をクリアした

2月末ごろに完全在宅勤務に移行した後に購入した「リングフィットアドベンチャー」を(一旦)クリアしました。

リングフィットアドベンチャーとは?

端的に言うと、筋トレRPGです。任天堂から2019年10月18日に発売されたNintendo Switch専用ソフトです。主人公になりきってステージを駆け巡り、フィットネスを通じて敵にダメージを与えます。

www.nintendo.co.jp

買ってどうだった?

リングフィットアドベンチャーとの出会いは、2019年12月の「Kaggle Days Tokyo」*1でした(?)。

非常に楽しいことは認知しており、今回の外出自粛の状況をきっかけに購入に至りました。

今なお続く在宅勤務中に買ったものは多い*2ですが、その中でも指折りの満足度と言えるでしょう。中盤あたりはストーリーの単調さに若干飽きましたが、筋トレにゲーム性を織り交ぜた構成で日々の運動習慣の継続に寄与しました。

4月からは動画配信サービス「TELASA」で『相棒』や『劇場版名探偵コナン』全シリーズを制覇していました*3が、そのお供になっていたのはリングフィットアドベンチャーでした。

あいにくのコロナ禍は、なかなか収まる気配を見せません。そんな状況下で、自宅で地道に取り組めるものを見つけられたのは良かったのかなと思っています。クリア後もまだまだお楽しみ要素があるようなので、引き続き筋トレを続けていきたいです。

*1:kaggledays.com

*2:なお未だに給付金の申請書は面倒くさくて書いていない

*3:upura.hatenablog.com

言語処理100本ノック 2020「59. ハイパーパラメータの探索」

問題文

nlp100.github.io

問題の概要

学習アルゴリズムとして「RandomForestClassifier()」も利用し「max_depth」の値を調整します。

import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score


X_train = pd.read_table('ch06/train.feature.txt', header=None)
X_valid = pd.read_table('ch06/valid.feature.txt', header=None)
X_test = pd.read_table('ch06/test.feature.txt', header=None)
y_train = pd.read_table('ch06/train.txt', header=None)[1]
y_valid = pd.read_table('ch06/valid.txt', header=None)[1]
y_test = pd.read_table('ch06/test.txt', header=None)[1]

test_acc = []

C_candidate = [0.1, 1.0, 10, 100]
for c in C_candidate:
    clf = LogisticRegression(penalty='l2', solver='sag', random_state=0, C=c)
    clf.fit(X_train, y_train)
    test_acc.append(accuracy_score(y_test, clf.predict(X_test)))


max_depth_candidate = [2, 4, 8, 16]
for m in max_depth_candidate:
    clf = RandomForestClassifier(max_depth=m, random_state=0)
    clf.fit(X_train, y_train)
    test_acc.append(accuracy_score(y_test, clf.predict(X_test)))

bestIndex = test_acc.index(max(test_acc))
if bestIndex < 4:
    bestAlg = 'LogisticRegression'
    bestParam = f'C={C_candidate[bestIndex]}'
else:
    bestAlg = 'RandomForestClassifier'
    bestParam = f'max_depth={max_depth_candidate[bestIndex - 4]}'

print(bestAlg, bestParam)

言語処理100本ノック 2020「58. 正則化パラメータの変更」

問題文

nlp100.github.io

問題の概要

学習時の「C」の値を調整することで、学習・予測結果が変わります。

import matplotlib.pyplot as plt
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score


X_train = pd.read_table('ch06/train.feature.txt', header=None)
X_valid = pd.read_table('ch06/valid.feature.txt', header=None)
X_test = pd.read_table('ch06/test.feature.txt', header=None)
y_train = pd.read_table('ch06/train.txt', header=None)[1]
y_valid = pd.read_table('ch06/valid.txt', header=None)[1]
y_test = pd.read_table('ch06/test.txt', header=None)[1]

C_candidate = [0.1, 1.0, 10, 100]
train_acc = []
valid_acc = []
test_acc = []

for c in C_candidate:
    clf = LogisticRegression(penalty='l2', solver='sag', random_state=0, C=c)
    clf.fit(X_train, y_train)
    train_acc.append(accuracy_score(y_train, clf.predict(X_train)))
    valid_acc.append(accuracy_score(y_valid, clf.predict(X_valid)))
    test_acc.append(accuracy_score(y_test, clf.predict(X_test)))

plt.plot(C_candidate, train_acc, label='train acc')
plt.plot(C_candidate, valid_acc, label='valid acc')
plt.plot(C_candidate, test_acc, label='test acc')
plt.legend()
plt.savefig('ch06/ans58.png')

f:id:upura:20200726004359p:plain