numpyで重み付き平均

つい最近まで、numpyやpandasには重み付き平均を求める関数は無いと勘違いしていて、
必要な時は自分で実装したのを使っていました。

データと重みが numpy の array で渡される場合だけ対応するのであればこのような関数で計算できます。
(listなどにも対応しようと思うとこれでは動きません)


def weighted_mean(data, weights):
    return np.sum(data * weights) / np.sum(weights)

しかしよくよく調べてみると、いつも使っている numpy.mean のほかにも、
numpy.averageという関数があって、これは引数にweightsを渡せるでは無いですか。
(averageの方が常に優秀というわけではなく、 meanにしか無い引数もあります。)

参考:
numpy.average
numpy.mean

numpy.average を使うと重み付き平均を手軽に計算できます。
せっかくなので適当なデータについて上の関数と結果を見比べましょう。
(ついでに計算直書きしたのも並べて確認しました。)


import numpy as np
data = np.array([1, 3, 7])
weights = np.array([5, 12, 2])
print(weighted_mean(data, weights))
# 2.8947368421052633
print(np.average(data, weights=weights))
# 2.8947368421052633
print((1*5+3*12+7*2)/(5+12+2))
# 2.8947368421052633

完全一致してますね。

WordPressでコメントのブラックリストを登録する方法

まだ公開しているコメントは一つもいただけていない本ブログですが、実はスパムのような投稿は日々投稿されています。
キーワードやIPアドレスにもだいぶ法則性が見えてきたのでそろそろ対策することにしました。

(日本語設定している場合)
Wordpressの管理画面の左ペインで、「設定」 => 「ディスカッション」 と選ぶと、
コメントブラックリスト という設定項目があります。

説明にある通り、ここによく貼られているURLやIPアドレスなどを入れておけばそのコメントは自動的にゴミ箱に入るようです。
このブログの趣旨に沿ったコメントであれば確実に登場しないような単語をいくつかピックアアプして設定しておこうと思います。

コメントの内容、名前、URL、メールアドレス、IP アドレスに以下の単語のうちいずれかでも含んでいる場合、そのコメントはゴミ箱に入ります。各単語や IP アドレスは改行で区切ってください。単語内に含まれる語句にも一致します。例: 「press」は「WordPress」に一致します。

NetworkXで最短経路探索

せっかくgraphvizではなくNetworkXを動かしているので、何か実装されているアゴリズムを試しておこうというのが今回の趣旨です。
とりあえずグラフ中の二点間の最短経路を探す関数を試してみましょう。
(SNSのフォローや友達関係のネットワークで共通の知り合い等を探すのに使えますね)

まず、ランダムにグラフデータを作成します。


import networkx as nx
import matplotlib.pyplot as plt
import numpy as np

G = nx.Graph()

for i in range(30):
    for j in range(i+1, 30):
        # 1/10の確率でnode間を線で結ぶ
        if np.random.randint(10) == 0:
            G.add_edge(i, j)

# 可視化
fig = plt.figure(figsize=(8, 8))
nx.draw_networkx(G)

出来上がったのはこちら。

ここで2点を指定して、最短経路を求めるにはshortest_pathを使います。
ドキュメント:Shortest Paths

パスが存在しない時にshortest_pathを実行するとエラーになるので、
パスの存在を判定する関数も合わせて試してみます。


# 特定の2つのnode間の最短経路を探す
print(nx.shortest_path(G, source=9, target=20))
# [9, 10, 13, 24, 12, 20]

# source か target を省略すると、その点と他の各点の最短経路を求める
print(nx.shortest_path(G, source=5))
# {5: [5], 6: [5, 6], 29: [5, 29], 21: [5, 6, 21]}

# パスが存在するか判定する
print(nx.has_path(G, source=6, target=10))
# False

NetworkXを動かしてみる

ネットワークやグラフ構造の可視化はgraphvizでできるのですが、分析となると、networkX等、別のライブラリを使う必要が出てきます。

このブログでnetworkXを取り上げるのは初なので、とりあえず今回は非常に単純なグラフを書いて可視化してみました。
チュートリアルを見ながらいろいろいじると楽しいのでオススメです。


import networkx as nx
import itertools
import matplotlib.pyplot as plt
# グラフオブジェクトの作成
G = nx.Graph()
# 5つの点を相互に全て結ぶ
for edge in itertools.combinations(range(5), 2):
    G.add_edge(*edge)
# さらに5つの点を追加し、既存の点と結ぶ
for i in range(5):
    G.add_edge(i, i+5)
# 後から追加した5つの点を環状に結ぶ
for i in range(4):
    G.add_edge(i+5, i+6)
G.add_edge(9, 5)
# 可視化
nx.draw_networkx(G)

出力結果がこちら。
点の配置に乱数が使われているので、何度か施行していい感じになったのを保存しました。

MySQLで実行中のクエリを確認する

タイトルの通り、MySQLで実行中のクエリ(正確にはプロセス)を確認するコマンドの紹介です。

プログラムがクエリの実行待ち等で止まってしまった時、
クエリが動いてるかどうか不安になる時などに使っています。

MySQLでは SHOW を使う専用の構文が用意されているので簡単です。


SHOW PROCESSLIST

ドキュメント:13.7.5.30 SHOW PROCESSLIST 構文

Pythonでワードクラウドを作成する

テキスト中の単語の出現頻度を可視化する方法として、ワードクラウド(word cloud)というのがあります。
要は頻出の単語ほどでっかい面積を占拠できるように可視化する方法ですね。

これをPythonで作る時、その名もズバリ wordcloudというライブラリがあり、非常に手軽に使うことができます。

リポジトリ: amueller/word_cloud

インストールはpipでできます。


$ pip install wordcloud

20newsgroups のデータを使ってやってみましょう。
あまりにもごちゃごちゃすると意味がわからないので、カテゴリを一個に絞ってやってみます。(今回は sci.electronics にしました)
細かいですが、STOPWORDS があらかじめ用意されているのもありがたいですね。


from wordcloud import WordCloud
from wordcloud import STOPWORDS
from sklearn.datasets import fetch_20newsgroups
import matplotlib.pyplot as plt
remove = ('headers', 'footers', 'quotes')
categorys = [
        "sci.electronics",
    ]
twenty_news = fetch_20newsgroups(
                                subset='train',
                                remove=remove,
                                categories=categorys
                            )
raw_data = twenty_news.data
wordcloud = WordCloud(
                            stopwords=STOPWORDS, background_color="white"
                        ).generate(" ".join(raw_data))

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)
ax.imshow(wordcloud, interpolation='bilinear')
ax.axis("off")
plt.show()

結果がこちらです。

itertools でリストの部分集合をリストアップする

Pythonの標準ライブラリである、itertools を使うと、リストや集合(set)の部分集合を手軽にリストアップすることができます。
ドキュメントはこちら。
itertools — 効率的なループ実行のためのイテレータ生成関数

欲しい部分集合の性質(重複の可否や、ソートの有無)に応じて、次の3種つの関数が用意されています。

itertools.permutations(iterable, r=None)
長さrのタプル列、重複なしのあらゆる並び

itertools.combinations(iterable, r)
長さrのタプル列、ソートされた順で重複なし

itertools.combinations_with_replacement(iterable, r)
長さrのタプル列、ソートされた順で重複あり

試しに要素が5個のリストを用意して、 r=3 でソートされた順番で重複無しの
部分集合をリストアップしてみましょう。
$_5\mathrm{C}_3=\frac{5\cdot4\cdot3}{3\cdot2\cdot1}=10$なので、10組みの結果が得られるはずです。


import itertools
data = list("ABCDE")

for subset in itertools.combinations(data, 3):
    print(subset)

# 以下出力
('A', 'B', 'C')
('A', 'B', 'D')
('A', 'B', 'E')
('A', 'C', 'D')
('A', 'C', 'E')
('A', 'D', 'E')
('B', 'C', 'D')
('B', 'C', 'E')
('B', 'D', 'E')
('C', 'D', 'E')

想定通りの組み合わせが出ました。
また、結果はsetやlistではなく、タプルで返されることも確認できます。

MySQLのテーブルをDB内でコピーする2つの方法

先日、諸事情あってMySQLのテーブルをコピーしておきたいことがありました。

SELECT以外の操作は滅多にやらないので、少し調べて出てきたのがこちらの方法です。
1つ目のクエリで元のテーブルと同じ構造のテーブルを作り、
2つ目のクエリでデータを移しています。


CREATE TABLE new_table_name LIKE table_name;
INSERT INTO new_table_name SELECT * FROM table_name;

今回必要だった要件は、これで満たせたのですが、一個のクエリでコピーする方法があったはず、
というおぼろげな記憶があったのでもう少し探して見つけたのがこちら。


CREATE TABLE new_table_name SELECT * FROM table_name;

これなら1行で中のテーブルをコピーできます。

なんで一行で済む2番目の方法よりも、クエリを二つ使う1番目の方法の方がよく使われているのか気になったので、後日実験してみました。

結果わかったのは、データはどちらの方法でも同じようにコピーされるのですが、
2番目の方法では、テーブルの設定の一部がコピーされないことです。

試したのがこちら。
まず、元のテーブル。


mysql> desc sample1;
+------------+--------------+------+-----+-------------------+-----------------------------+
| Field      | Type         | Null | Key | Default           | Extra                       |
+------------+--------------+------+-----+-------------------+-----------------------------+
| id         | int(11)      | NO   | PRI | NULL              | auto_increment              |
| name       | varchar(255) | YES  |     | NULL              |                             |
| created_at | datetime     | NO   |     | CURRENT_TIMESTAMP |                             |
| updated_at | datetime     | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+------------+--------------+------+-----+-------------------+-----------------------------+

1番目の方法でコピーしたテーブル。こちらは元のテーブルと同じです。


mysql> desc sample2;
+------------+--------------+------+-----+-------------------+-----------------------------+
| Field      | Type         | Null | Key | Default           | Extra                       |
+------------+--------------+------+-----+-------------------+-----------------------------+
| id         | int(11)      | NO   | PRI | NULL              | auto_increment              |
| name       | varchar(255) | YES  |     | NULL              |                             |
| created_at | datetime     | NO   |     | CURRENT_TIMESTAMP |                             |
| updated_at | datetime     | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+------------+--------------+------+-----+-------------------+-----------------------------+

2番目の方法でコピーしたテーブル。
auto_incrementやDefaultの値の一部、主キー属性などがコピーされていません。


mysql> desc sample2;
+------------+--------------+------+-----+-------------------+-----------------------------+
| Field      | Type         | Null | Key | Default           | Extra                       |
+------------+--------------+------+-----+-------------------+-----------------------------+
| id         | int(11)      | NO   |     | 0                 |                             |
| name       | varchar(255) | YES  |     | NULL              |                             |
| created_at | datetime     | NO   |     | CURRENT_TIMESTAMP |                             |
| updated_at | datetime     | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+------------+--------------+------+-----+-------------------+-----------------------------+

値はどちらの方法でもコピーされるのですが、
見落としがちな部分に差異があるので注意が必要ですね。

people analytics tokyo #1に参加しました

少し前になりますが、6月28日にYahoo!のコワーキングスペースのLODGEで開催されたpeople analytics tokyo #1 に参加してきました。
イベントのページ

3つのトークと3つのLT全て資料が公開されていてありがたい。

Talk

Drive Business Performance by People Analytics – 国内外の最新トレンドと事例を添えて – -BtoA株式会社 代表取締役 石原史章さん
Rによるemailコミュニケーションの可視化 – ソフトバンク株式会社 御園生銀平さん
感謝ネットワークからみる組織のコミュニケーションの形 – People Analyst 大成弘子さん

LT

People Analyticsと社会ネットワーク分析 – フリーランス 祖山寿雄さん
組織の「価値観」ネットワークの可視化と「評価」の関係性 – 株式会社トランス 代表取締役 塚本鋭さん
群衆の知恵・集団的知性 〜 会社で集まる意味あんの?〜 – 株式会社シンギュレイト 代表取締役 鹿内学 さん

感想

自分は人材系のサービスの会社でデータサイエンティストをやっているのに、
なかなかこの分野の分析には取り組めていない反省もあって参加を決めました。
結果的には参加して大正解でした。

1. ネットワーク分析が持つポテンシャルの高さを実感した

ネットワーク分析についてはそういうものがあるということだけは知っていましたが、
サンプルコード等を動かしてみても、それをどう使えば業務に役立つ(=利益が出る)のかわからず、学習が進んでいませんでした。
しかし、今回の発表では多くのテーマの中でネットワーク分析が使われており、
そのネットワークも、メールやりとり、価値観の近さ、上司と部下などの組織体制、会話中の単語の共起など、様々な種類のものでした。
そしてそのいずれでも興味深い知見が得られおり、この手法が持つ可能性の高さを実感しました。

2. 分析から得られた知見が面白い

退職者の予測と対策や従業員の不正の予防、価値観と仕事におけるパフォーマンスの関係、
上司の語彙と部下のエンゲージメントの関係など、組織で働く人なら誰でも興味をもちそうな結果が紹介され、
結果だけを取ってみても面白いものばかりでした。

3. 会場全体の一体感が心地よかった

普段はそれぞれ別々の分野の分析をしている人が、各自自分の好きなことを話すタイプのMeetUpが好きでよく参加しているのですが、
今回の会はそれとは逆に、非常に全体の統一感の高い会でした。
しかし、全体に一体感があり会場の雰囲気の良さもあり、とても心地の良い時間を過ごせました。
ネットワークの種類は違っても多くの人がネットワーク分析の手法を使い、人に関する分析を行うという、
共通のテーマを持って集まっているからこそだと思います。
大成さんの発表の中で、ピープルアナリティクスとは、働く人々を幸福にする分析である、という言葉がありましたが、
そこに取り組む人たちの集まりだったということも大きく影響していると思います。

4. おまけ

Yahoo LODGE に興味があり、いつか行きたいと思ってたので行けてよかった。

まとめ

全然知識を持ってない分野のミートアップでしたが、思い切って参加してみてよかったと思います。
ネットワーク分析という新しいおもちゃを手に入れて日々の勉強がまた楽しくなりそうです。
自分の職場のデータでも何かやってみたい。

ピープルアナリティクスについて、日本語で具体的な例を学べる場が少ないということで今回の会を開催されたそうですが、
僕に取って非常にありがたいきっかけになりました。
会の準備も当日の運絵も大変だったと思いますが、主催者・発表者の皆様、本当にありがとうございました。

2019年第2四半期によく読まれた記事

ブログを開設から半年になりました。
ありがたいことに訪問者も増え、週に400人以上の方が訪問してくださっています。
開設から3ヶ月経過した時点でよく読まれている記事を紹介させていただいた後、
再び3ヶ月たちましたのでこの辺で最近の人気記事を紹介させていただこうと思います。

前回:2019年第1四半期によく読まれた記事

ベスト5はこちらです。

  1. macにgraphvizをインストールする
  2. pythonで編集距離(レーベンシュタイン距離)を求める
  3. pythonでARモデルの推定
  4. pandasでgroupbyした時に複数の集計関数を同時に適用する
  5. Mac(Mojave) に pip で mecab-python3をインストールする時にはまった

前回1位のmecab-python3のインストール時エラーの話題は5位になり、
代わりに前回2位のgraphvizのインストールの話がトップになりました。

前回、ほぼ読まれてないとぼやいていた時系列解析系の話から、ARモデルの話が3位に入ったのは少し嬉しいです。
ただ、自己回帰ではなく拡張現実のARと勘違いして訪問された方もいらっしゃるような気がしています。

まだまだブログに書きたいことはいろいろあるのですが、
1日1記事のペースで書けるようなネタ(以前検証した時のコードが手元にあるものや、ちょっとした小ネタ的なもの)は
流石に180記事も書いていると枯渇気味で、もうちょっと1記事1記事に時間かけた方が良さそうという思いは強くなっています。
ということで、今後は1週間あたりの記事数を少し落としながら継続していこうと思います。