初心者データサイエンティストの備忘録

調べたことは全部ここに書いて自分の辞書を作る

【読書記録】社長の哲学

本書を手に取った理由

仕事に向き合う自分の態度を改善するためです。
ここ最近、自分の仕事に対する心構えや志をなんとかしたほうが良いのではと思うことが多々あります。そこで、歴代のカリスマ経営者が書いた本を読み漁っています。
本書はその3冊目です。
ちなみに過去2冊の書評はこちら。

aisinkakura-datascientist.hatenablog.com

aisinkakura-datascientist.hatenablog.com

今回は本書を読んで印象に残った言葉について書いていきたいと思います。

気が狂ったかと思われるぐらいにならなければいけない

タクシー会社であるMKグループオーナ青木さんの言葉です。

なぜそこまでするのかといえば、いいものを作るためには一心不乱、気が狂ったかと思われるぐらいにならなければいけないからです。そこまでしないと決していいものはできない。

これは、青木さんが毎日(!)無線を通じて社員教育をしていることについて述べたものです。毎日無線を通じて全社員に教育を行っているから、お客様に良いサービスを提供できていると青木さんは言うわけです。
私はこれを読んで、「ああやっぱりそうなのか」と思いました。要領よく仕事をする方法について書かれた本が世の中にたくさんありますし、手際よく仕事をする人が世の中褒められます(そう感じているのは私だけかもしれませんが)。私はそういった情報を鵜呑みにし、なんとか要領よくできないかということばかりを考えていました。しかし、社長になるような人でもお客様に良いサービスを提供するために、こういった泥臭い努力を重視し、また実践していることは私にとって非常に印象的でした。

プールに水一滴の努力を惜しまない

イエローハット相談役の鍵山さんの言葉です。

「この秋は雨か嵐か知らねども今日の勤めに田の草を取る」という歌があります。さんざん炎天下で骨を折っても、もしかしたらこの秋は大雨が降るかもしれない。大嵐になるかもしれない。日照りになって収穫がないかもしれない。しかし、そういうことは別として、とにかく今日の自分の勤めとして田の草を淡々として取っていく。そういうことを教える歌です。(中略)たとえば、コップ一杯の努力を皆さんがなさったとします。そのとき、もう一滴分だけ足しなさいといわれて、それをやる人はいないのです。なぜかというと、そこに一滴足しても増えたかどうかわからないからです。(中略)どうか結果や未来を常に約束されたり保証されたりすることを期待しないでください。何もないことを、むしろ危険だけ待ち構えていることを、「それでも私は正しいと信じたからにはやる」という考え方を持っていただきたいと思います。

努力についての言葉ですが、この考え方は私のような不器用なタイプに合致していると思いました。というのも、私は要領が悪く、ゴールを明確にして努力するということができません。大きく回り道をしながら結果的に人より劣った地点にゴールするということが過去には多々ありました。 そのため、人よりも多く努力しなければ、生き残ることができません。
また、水一滴分という言葉の意味ですが、これは努力量ではなく、成果の比喩だと私は解釈しました。コップ一杯分の努力と、そこに水一滴を追加するのに必要な努力の量が同じでも、努力すべきというのが鍵山さんの言いたいことだと私は解釈しました。
私は器用に立ち回れないタイプなので、水一滴分の努力を惜しまないようにしたいです。

一杯のコーヒーを通じて安らぎと活力を提供する

ドトールコーヒー社長の鳥羽さんの言葉です。以下のような、「使命」に関する文脈の中でこの言葉は紹介されました。

(前略)「喫茶業が世に存在する意義はなんなのだろうか」ということを考えました。(中略)そして、「一杯のコーヒーを通じて安らぎと活力を提供することが喫茶業の使命ではないのか」と感じました。(中略)喫茶店の店長に任命された十九歳のときに、「喫茶業とは一杯のおいしいコーヒーを通じて人々に安らぎと活力を与え、お客様を建設的な方向へと向かわせるものでなくてはならない」と感じました。

鳥羽さんが最初に勤めていた会社で、喫茶店の店長を任された際の言葉です。
この文章を読み、データサイエンティストの使命というものを考えてみました。 私はデータサイエンティストの役割を「分析結果を基にしたコンサルティングを行い、お客様の成果を上げる」ことだと考えています。しかし、現状私が行っている仕事は、お客様の御用聞きのような状態に留まっており、「お客様の成果を上げる」ことまで結びついていないと感じています。お客様が分析結果を見て動き出してしまう。そんな状態を私は目指したいと思いました。
なお、お客様が分析結果を見て動き出してしまうためには、分析結果に驚きと納得の両方が兼ね備わっている必要があると私は考えています。そのような分析をコンスタントに生み出すためにはコンサルティング・技術力の両方が必要です。両方のスキルを高めるための努力を惜しまないことが、現在の私にできることかと考えています。

本のセールスができなかった幸せ

ダイソーの社長の矢野さんの言葉です。

東京へ出た私は、図書月販という会社で本のセールスをはじめました。(中略)私には本を売る力が全くありませんでした。(中略)自分には運がないけれど能力もないんだなということを、骨の髄まで思い知らされました。でも、あとになって思うと、あのときなまじセールスがうまくできて、二十四人中十位くらいにでもなっていたら、きっと故郷の広島に帰ってどこかの営業所に入り、そこの所長になることを目指していたでしょう。そして、そのまま図書月販にとどまっていたらどうなっていたでしょうか。図書月販はその後ほるぷという会社になりますが、五、六年前につぶれてしまいました。あのとき本のセールスができなかった幸せというものを、今感じるのです。

矢野さんは、本のセールスをしていましたが、全くうまくいきませんでした。その経験を「当時はノイローゼになりながら働いていたものの、今ではできなかったことが幸せ」と振り返っています。
私が本書を読んでいたとき、ちょうど会社の人事評価が発表された時期でした。正直、私は昇格すると思っていたのですが、実際には昇格できませんでした。その際悔しく、情けない気持ちになり、非常に落ち込みました。ですが、今回昇格できなかったことが後々自分の幸せにつながることもあるのではないかと、矢野さんの言葉を通じて感じることができました。
上でも書いたように私は現時点でデータサイエンティストの使命を十分に果たせていると思っていません。もし昇格していた場合、私はそのことに満足してしまい、データサイエンティストの使命を果たすということに対して生半可な気持ちになっていたと思います。それが、今回昇格できなかったことで、より熱心に使命を果たそうと思うことができました。昇格できなかったことが、より努力する契機となったのです。5年後に「あの時昇格できなくて良かったな」と言えるよう努力を積み重ねていきたいと思いました。

まとめ

「社長の哲学」に載っていた私にとっての印象的な言葉をまとめました。 それぞれの言葉は下記のように分類できると思います。

  • 努力に関する言葉
    • 気が狂ったかと思われるぐらいにならなければいけない
    • プールに水一滴の努力を惜しまない
  • 使命・志に関する言葉
    • 一杯のコーヒーを通じて安らぎと活力を提供する
  • うまくいかないことへの心構えに関する言葉
    • 本のセールスができなかった幸せ

この本で得られた考え方は実践してなんぼです。仕事が辛くなったときに、このブログや本書を読み返して実践していきます。

【旅行記】那須塩原・喜連川温泉(観光地編)

2022年3月下旬に栃木に行きました。今回は行って良かった観光地について書きたいと思います。

ちなみに、宿について書いた記事はこちらです。

aisinkakura-datascientist.hatenablog.com

千本松牧場

まずは千本松牧場。ここは初日に行った那須塩原かんぽの宿の近くにありました。

www.senbonmatsu.com

千本松牧場で印象的だったのは、お土産屋さん! お酒やおつまみになるもの、甘味、ご飯のお供になるものまで充実度が半端なかったです。

www.senbonmatsu.com

今回は時間がなく、乗馬などのアクティビティは全くできませんでした。今度はこの千本松牧場を目当てにして訪問しても良いと思いました。

やいた里山いちご園

子どもがいちご大好きなので、いちご狩りもしてきました。 来訪した「やいた里山いちご園」は、かんぽの宿喜連川と提携しており、宿泊とセット料金で少しお安くなりました。

e15.oshima-cf.com

50分間食べ放題だったのですが、子どもはずっと食べていました。かわいいですね。

洞窟酒造

次に訪れたのは島崎酒造。この酒屋さんは、第二次世界大戦の際に掘られた地下工場を利用してお酒を熟成させています。 そのため、洞窟酒造と呼ばれています。

この洞窟酒造では、将来に向けてオーナーズボトルを注文できます。 注文したオーナーズボトルは、この洞窟で熟成させたのちに家に送られてきます。 私たちも20年後に子どもと一緒に飲めるように1本注文しました。

azumarikishi.co.jp

道の駅にのみや

最後に、道の駅にのみやに行きました。本当は別の場所に行く予定だったのですが、あいにく営業していなかったため急遽予定を変更して来訪しました。
道の駅と言えば、地元の野菜が多いイメージですが、ここでは大量のいちごが売られており、さすが栃木と思いました。 お土産も色々売っていましたが、レストランやパン屋もあり、駐車場には地元ナンバーの車も多かったです。 私たちはレストランで、いちごカレーを食べました。
地元に愛される道の駅といったイメージです。

michinoeki-ninomiya.jp

まとめ

子ども連れの旅行で、予定外のこともありましたが、その分意外な発見があり面白かったです。 特に千本松牧場を怪我の功名的に発見できたことが私は満足です。次回訪れるときは、千本松牧場を目的地の一つとし、色々なアクティビティにも参加したいですね。

【旅行記】那須塩原・喜連川温泉(宿編)

2022年3月下旬に栃木に行きました。本記事はその旅行記です。 栃木旅行については、宿と観光地に分けて書こうと思います。

今回の栃木旅行では、宿の予約がギリギリになってしまい、同じ宿に連泊できませんでした。 そのため、かんぽの宿 塩原とかんぽの宿 栃木喜連川温泉をはしごすることにしました。

かんぽの宿 塩原

初日はかんぽの宿 塩原に行きました。 www.kanponoyado.japanpost.jp

部屋に入って驚いたのが、洋室と和室両方があること!

あまりに広かったからか、子どもも大興奮!はしゃいで部屋の中を走っていました。

また、旅行前から楽しみにしていたのは、部屋付きの露天風呂です。

この露天風呂は泉質が良いのか、少し湯船に浸かっただけで体がポカポカ。 温泉を出た後も全く湯冷めしないと夫婦で感動しておりました。

この部屋はバリアフリーだったので、車椅子の方たちにとっても使いやすいのではないかと思います。

夕飯も美味しくどれもこれも一工夫ありました。(肝心の夕飯の写真は、はしゃぐ子どもへの対応に忙しく撮影することができませんでした。)

かんぽの宿 喜連川温泉

2日目は喜連川温泉のかんぽの宿に宿泊しました。
www.kanponoyado.japanpost.jp

部屋は塩原に比べると小さいですが、見晴らしが良かったです。

また、こちらの宿で非常に良かったのは共用キッズルーム! 色々な遊具やボールがたくさん置いてあり、子どもは大満足でした。

そして、喜連川は料理も美味しかったです。

個人的には、人生で初めて食べた伝法焼きの出汁の美味しさとふわふわした食感に感動しました。

まとめ

今回は塩原と喜連川温泉の2つのかんぽの宿に行きました。部屋は塩原、料理は喜連川温泉が良いというのが私たち夫婦の感想です。
また、温泉はどちらも素晴らしく、温泉好きの私にとっては最高でした。 今度は釣り具をもって来訪したいですね!

【Python】クロージャを使ったメモイズ機能についてのメモ

Pythonクロージャについて調べていたときに、下記の記事を発見した。

www.lifewithpython.com

この記事の中の以下のコードの挙動がうまく理解できなかったので、色々調べてみた。

def fibonacci_func():
    """フィボナッチ数列を返す関数を返す メモイズ機能つき"""
    table = {}  # 計算済みのフィボナッチ数列を格納するテーブル

    def fibonacci(n):
        # 計算したことのある数値についてはテーブルを参照して返す
        if n in table:
            return table[n]

        # 計算したことのない数値についてはフィボナッチ数列の定義どおり計算
        if n < 2:
            return 1
        table[n] = fibonacci(n - 1) + fibonacci(n - 2)
        return table[n]

    return fibonacci

f = fibonacci_func()
print(f(30))

結論としては、メソッドfibonacci内で使い回すtableをfibonacciの外で定義しているということ。 したがって、下記のfibonacci2と挙動は同じ

def fibonacci2(n, table={}):
    # 計算したことのある数値についてはテーブルを参照して返す
    if n in table:
        return table[n]

    # 計算したことのない数値についてはフィボナッチ数列の定義どおり計算
    if n < 2:
        return 1
    table[n] = fibonacci2(n - 1) + fibonacci2(n - 2)
    return table[n]

ちなみに、このメモイズ機能を使うことで自分のPCの場合、30番目のフィボナッチ数列の値を計算する時間が 300msから10msに短縮された。

【旅行記】新島・式根島(3日目)

新島・式根島の3日目の旅行記です。
3日目は、式根島観光と式根島→新島→帰宅の移動がメインになりました。

(1日目と2日目の記事はこちら)

aisinkakura-datascientist.hatenablog.com

aisinkakura-datascientist.hatenablog.com

式根島観光

この日は、時間もあまりなかったのでサクッと行けるところだけ行きました。

湯加減の穴

まず最初に行ったのが、湯加減の穴。

この穴は、地中の源泉とつながっているらしく中が温かいです。 穴の入り口のコケもあたたかな蒸気に包まれているせいか水滴がついており、とてもきれいでした。

みやとら

式根島に行くと毎回食べてしまうのが、みやとらの「たたき丸」。 たたき丸とは、具をご飯で包んで揚げたものです。コンビニのホットスナックのような感覚で食べられます。 www.miyatora.com

この日は、「あしたば佃煮」をチョイス。あしたばの香りがご飯全体にいき渡っており、美味しいです。 佃煮の塩加減も絶妙。
私の子どもは1歳半なので食べられませんでしたが、子どもにもウケる味だと思います。

式根島→新島

前日の「にしき」に懲りた妻の要望で、大型船「さるびあ丸」で式根島→新島の移動をすることになりました。 さるびあ丸は2020年に新造船が就航しており、私も乗ったことがありませんでした。 今回は式根島→新島間の約20分の短い移動ですが、楽しみです。

新しいさるびあ丸に乗ってあまりの変わりように色々びっくりしましたが、一番驚いたのが2等椅子席! めちゃくちゃ奇麗になっていました。

この椅子席ですが、ブラインドを降ろすことで半個室状態にできます。また、席のすぐ傍には鍵付きロッカーもありセキュリティ万全。 旧さるびあ丸の2等椅子席は正直嫌いでしたが、この新造さるびあ丸の2等椅子席だったら積極的に使いたいなと思いました。

新島ランチ

さるびあ丸で新島に到着してから飛行機の時間まで4時間くらいありました。 なので、ランチを新島で食べます。

訪れたのはPOOL。めちゃくちゃお洒落な喫茶店でした。 tabelog.com

スパゲッティやガパオライスも美味しかったのですが、特に美味しかったのがデザートの桜シフォンケーキ。

シフォンケーキは、桜の塩漬けを使っているのか少し塩味があり、生クリームとの相性が抜群でした。

式根島で子どもが楽しめる場所

最後にこの旅行を振り返って、我が家の1歳半の子どもが楽しめた場所をまとめます。

  • クジラ公園(小の口公園)

  • 雅湯

クジラ公園と雅湯は2日目の記事に書いたので、興味ある方はご覧ください。

aisinkakura-datascientist.hatenablog.com

  • ぐんじ山展望台
    山道を登って見に行く展望台ですが、遊歩道から展望台までの距離はどれほど長くないため、子連れでも展望台に行くことができました。 写真を撮り忘れたので、HPを載せておきます。

shikinejima.tokyo

  • 清水屋
    今回お世話になったお宿です。漫画が大量にあります。 さすがに1歳半の子どもだと漫画は読めませんが、小学生以上の子どもであればかなり楽しめると思います。 小説も多いので、大人も飽きることはないと思います。

shimizuya.tokyo

【読書記録】俺がつくる!

本書を手に取った理由

基本的には「論語と算盤」のときと同じです。 aisinkakura-datascientist.hatenablog.com

ですが、今回はより身近(?)な人の本を選んでみました。私は子どものころ成長ホルモンが不足しており、その治療のために毎日注射をしていました。 そのとき使っていた「痛くない注射針」を開発した方が、本書の著者である岡野雅行さんです。

一から十までできるようになれ

本書で岡野さんは、「職人は一から十までできるようになれ」と主張しています。その理由は、一つのことしかできない職人はリストラされると食べていけなくなるからです。

この点は、データサイエンティストと同じです。巷でデータサイエンティストに必要な能力は「コンサル力」「サイエンス力」「エンジニアリング力」と言われています。これらのどれかが欠けるとデータサイエンティストとしてのアイデンティティが失われるでしょう。

なお、データサイエンティストが取り組む業務の中で、各スキルがどの場面で必要か自分なりにまとめてみたのが図1です。(各スキルの定義をちゃんと提示していないので違和感ある方もいらっしゃると思いますが...) 各スキルの替えはきくけれど、全てのスキルを持っているという意味では替えがきかないのがデータサイエンティストだと理解をしています。

図1:スキルマップ

プラントとして売る

著者の岡野さんは、金型とプレス機、ノウハウをセットにし、プラントにして大企業に卸すという商売の仕方をしています。

先述した「一から十までできるようになれ」という言葉には、職人という個人単位での意味合いもありますが、企業としても「一から十までできる」ことでこのような売り方ができるのだと理解しました。

自分の学び方

「一から十までできるようになる」には、体力(企業でいえば資本力)が必要です。また、企業が大きくなればなるほど、各領域に必要なスキルが深化し、「一から十までできるようになる」の実現が不可能になります。 そのため、個人や企業によっては特定の専門分野に特化した成長の仕方をすることが、賢い方法なのだと思います。

その上で私としては、やはり「一から十までできるようになる」を目指したいと思っています。それは、「一から十までできるようになる」状態を目指して努力する過程で、特に力を入れていきたい分野を見つけることができるのではと考えているからです。

自分の取り組みとしては、「一から十までできるようになる」を目指す。これを胸に刻んで邁進していこうと思います。

分割型クラスタリングの実装

記事の内容

Macnaughton Smith et al. (1965)が提案した分割型クラスタリングPythonで実装しました。本記事では、そのスクリプトを公開します。

分割型クラスタリングとは

分割型クラスタリングの定義を例によってカステラ本から引きます。

分割型クラスタリング法では、全データが属する一つのクラスタから開始し、トップダウンに、一つの既存クラスタを二つの子クラスタ再帰的に各反復で分割していく。

つまり、一つの大きなクラスタを複数のクラスタに分割していく手法が分割型クラスタリングです(図1)。デンドログラムを上から作っていくイメージのクラスタリングです。

図1:分割型と凝集型クラスタリングの違い

実装する手法の説明

アルゴリズムの概要

Macnaughton Smith et al. (1965)が提案したクラスタリング手法を、またまたカステラ本から引きます。

この手法では、まず全観測を一つのクラスタGに配置する。次に、他の観測との平均非類似度が最も大きい観測を選ぶ。この観測を2番目のクラスタHの最初の要素とする。続く各ステップにおいて、Gに含まれる観測のうち、Hまでの平均距離からG中の残りの観測との平均距離を引いた値が最も大きいものをHに移す。この平均の差が負になるまで、この操作を続ける。つまり、平均してH中の観測により近い観測は、Gには含まれなくなる。この結果、もとのクラスタは、Hに移した観測集合とGに残った観測集合の二つの子クラスタに分割される。(中略)その後の各階層は、前の階層のクラスタの一つにこの分割手順を適用して生成することができる。

このように、文章でアルゴリズムを説明されてもわかりにくいと思うので、図に直します(図2)。

図2:実装するアルゴリズムのイメージ

クラスタ内の他の観測と比較して離れているものを別のクラスタに移動していくイメージです。

実装!

構成

作成するスクリプトの構成は下記の通りです。

フローチャート

アルゴリズムPythonに落とし込むためにフローチャートを作成します。

図3:実装するアルゴリズムのフロチャート

スクリプトフローチャートの対応関係

図3のフロチャートとスクリプトの細かな対応を見ていきます。以下、フロチャート→スクリプトの順に書いていきます。

まず、距離行列を作成する箇所ですが、これはmakeDistanceMatrix関数で実現します。

# 距離行列を作成する関数
    def makeDistanceMatrix(self):
        # 各データの組み合わせを作成
        combination_df = self.df-self.df[:, np.newaxis]
        
        # 組み合わせごとに距離を計算
        d_matrix = np.array([[np.linalg.norm(combination_df[i, j]) for i in range(0, len(self.df))] for j in range(0, len(self.df))])
        
        # 出力
        self.d_matrix = d_matrix 
        return(d_matrix)

次に、クラスタリング結果を格納するDataFrame作成ですが、これについては特別なことをしていません。初期値として0行目に元のクラスタのインデックスを格納しておきます。

# クラスタリング結果を格納するDataFrame定義
        tmp0_result_DF = pd.DataFrame(columns=["tmp_cluster_no", "cluster", "diff", "children"])

        # 0行目は元のクラスタのインデックスを格納
        tmp0_result_DF.loc[0] = [0, list(range(0, len(self.df))), np.nan, np.nan]

さらに本命の分割をする部分です。ここはいくつかの関数に分けて実現しました。

# クラスタに含まれる要素の個数で分割の方法を変える
        while (count < len(self.df)-1):    
            if len(tmp0_result_DF.iloc[next_clustering]["cluster"]) >= 2:
                clusterSupoort(rdf = tmp0_result_DF, nc = next_clustering, cd = clustering_done)
                count += 1
            else:
                clustering_done.append(next_clustering)
        
            next_clustering = min([i for i in tmp0_result_DF.index if i not in clustering_done])

アルゴリズム中の「分割」については、doClusteringCore関数で実現していますが、その中身はこちら。

def doClustering(self):
        
        # クラスタを分割する関数
        def doClusteringCore(target):
            G1 = target.copy() # 移動元
            G2 = [] # 移動先

            # 移動すべき1個目の点を決める
            tmp_d_matrix = self.d_matrix[np.ix_(G1, G1)]
            maxi = np.argmax(
                np.apply_along_axis(
                    np.mean,
                    axis = 0,
                    arr = tmp_d_matrix)
            )
            
            # cluster1のmiをcluster2に移動するメソッド
            def move(cluster1, cluster2, mi):
                trans_data = cluster1[mi]
                cluster1.remove(trans_data)
                cluster2.append(trans_data)

            # 点の移動
            move(cluster1 = G1, cluster2 = G2, mi = maxi)
    
            while True:
                # 2個目以降の移動すべき点を決める
                ## G1の各点の他の点との平均距離
                tmp_d_matrix = self.d_matrix[np.ix_(G1, G1)]
                G1_mean = np.apply_along_axis(
                    sum,
                    axis = 0,
                    arr = tmp_d_matrix
                )/(len(G1)-1)

                ## G1の各点とG2の各点の平均距離
                tmp_d_matrix = self.d_matrix[np.ix_(G1, G2)]
                d_g1tog2 = np.apply_along_axis(
                    np.mean,
                    axis = 1,
                    arr = tmp_d_matrix
                )

                ## 「G1の各点の他の点との平均距離」-「G1の各点とG2の各点の平均距離」
                criterion = G1_mean-d_g1tog2

                ## 判定条件の作成
                cond = (criterion <= 0)
                tf = all(cond)
                
                # 判定がTrueの場合、while文から抜け出し、分割終了
                # 判定がFalseの場合、点を移動させ分割続行
                if tf:
                    break
                else:
                    maxi = np.argmax(criterion)
                    move(cluster1 = G1, cluster2 = G2, mi = maxi)
                    
            cluster_diff = np.sum(self.d_matrix[np.ix_(G1, G2)])/(len(G1)*len(G2))
            
            # 出力
            return([G1, G2, cluster_diff])

最後に出力をdendrogram関数に組み込めるような形に変形します。

# dendrogramに読み込める形に変更する
        tmp0_result_DF["group_n"] = [len(l) for l in tmp0_result_DF["cluster"]]
        
        ## 要素が2個以上のクラスタに対する処理
        tmp1_result_DF = (
            tmp0_result_DF[tmp0_result_DF["group_n"] >= 2]
            .sort_values("diff", ascending=False)
        )
        cluster_n = len(self.df)
        tmp_cluster_no_list = list(range(cluster_n, 2*cluster_n-1))
        tmp_cluster_no_list.reverse()
        tmp1_result_DF["cluster_no"] = tmp_cluster_no_list
        
        ## 要素が1個のクラスタに対する処理
        tmp2_result_DF = (
            tmp0_result_DF[tmp0_result_DF["group_n"] == 1]
        )
        tmp_cluster_no_list = [l[0] for l in tmp2_result_DF["cluster"]]
        tmp2_result_DF["cluster_no"] = tmp_cluster_no_list
        
        ## 要素が2個以上と1個のクラスタに対するresult_DFをunion
        tmp3_result_DF = (
            tmp1_result_DF
            .append(
                other = tmp2_result_DF,
                ignore_index = True
            )
        )
        
        ## childrenから新規に振ったcluster番号を用いてクラスターを表現
        cluster_list = []
        for c in tmp3_result_DF["children"]:
            if isinstance(c, list):
                tmp_series = tmp3_result_DF.loc[tmp3_result_DF["tmp_cluster_no"] == c[0], "cluster_no"]
                c0 = tmp_series.iloc[-1]
                tmp_series = tmp3_result_DF.loc[tmp3_result_DF["tmp_cluster_no"] == c[1], "cluster_no"]
                c1 = tmp_series.iloc[-1]
                
                cluster_list.append([c0, c1])
            else:
                cluster_list.append(np.isnan)
        
        tmp3_result_DF["cluster_list"] = cluster_list
        
        ## 必要なカラムだけ抜き出し、少し処理
        tmp_linkage_Z = (
            tmp3_result_DF[1<tmp3_result_DF["group_n"]]
            .sort_values("cluster_no", ascending=True)
        )
        
        tmp_Z = []
        for i in tmp_linkage_Z[["cluster_list", "diff", "group_n"]].itertuples():
            i0, i1 = i.cluster_list
            d = i.diff
            n = i.group_n
            tmp_Z.append([i0, i1, d, n])
            
        Z = np.array(tmp_Z)

以上がスクリプト全体です。細切れだと使いにくいと思うので、全体を載せたgitのリンクも貼っておきます。リンク先のIn[6]が該当箇所です。

github.com

テスト

簡単なテストデータに対して、作成したスクリプトを動かしてみました。

# テスト用データ作成
test_data = np.array([
    [1, 1],
    [2, 2],
    [3, 3],
    [4, 4],
    [5, 5],
    [6, 6],
    [7, 7],
    [9, 9]
])

# 実行
## インスタンス作成
test_class = MacnaughtonClustering(df = test_data)

## 距離行列の作成と表示
print(test_class.makeDistanceMatrix())

## クラスタリング実行
result = test_class.doClustering()

## デンドログラム作成
dendrogram(result)

上記のスクリプトで作成したデンドログラムは、図4です。狙った通りのデンドログラムが作成できました。

図4:デンドログラム

参考文献