4月の人事異動一覧で社内人材を可視化しようーネットワーク科学

:::note info
- 社内で比較的手に入りやすい人事異動一覧で社内人材を可視化する
:::
- 進まないデータドリブン経営
    - データドリブン経営なんて言っても全然進んでいないじゃ無いですか。色々できるのにね

ということでその一発目として社内で比較的手に入りやすいデータである、人事異動一覧を用いて人材を可視化します。私の会社だとPDFで一覧が出ているのですが、まーPythonのPDF系ライブラリでParseできますよね。

:::note info
- 「弱いつながりの強さ」の重要性
:::
- ネットワーク科学の入り口としてまずは可視化
    - グラノヴェターという人が提唱しているのですが、強いつながりは強くて良さそうだけれど同じ情報を持っているだけ、弱いつながりは意外な情報をもたらして弱いつながりをたくさん持っているようがいいんじゃね？、みたいな考え方です

https://note.com/aokikenichi/n/n0c51ad7c0b6c

# データ
私の会社の人事異動一覧を晒すわけにもいけないので、架空データを今回は作成します。

## 仕様
- Python
- 社員300人
    - 役職は省略
    - 部署は部と課のみ
        - バックオフィス部　20%
        - 営業部　40%
        - 技術部　40%
    - それぞれの部内で3-10名をランダムで1つの課員
    - 同姓同名はなしで氏名がキー
- 異動
    - 先期と今期のみ
    - 10%程度の人が別部署に異動
        - うち7%が同じ部内の別の課へ
        - 3%が別の部へ
- 人事異動の一覧のフォーマット
    - 氏名, 旧部署, 新部署

ChatGPTに作ってもらってこんな感じ、

```python
import random
import csv

num_total = 300
departments_info = {
    "バックオフィス": 60,  # 20%
    "営業": 120,           # 40%
    "技術": 120            # 40%
}

# 氏名生成用リスト
family_names = ["佐藤", "鈴木", "高橋", "田中", "渡辺", "伊藤", "山本", "中村", "小林", "加藤", "吉田", "山田", "佐々木", "山口", "松本", "井上", "木村", "林", "斎藤", "清水"]
given_names = ["太郎", "花子", "次郎", "美咲", "健一", "直子", "一郎", "由美", "大輔", "真理子", "直樹", "恵子", "隆", "智子", "弘", "恵", "孝", "京子", "秀樹", "麻衣"]

# ユニークな氏名のリストを生成（重複なし）
all_possible_names = [f + g for f in family_names for g in given_names]
random.shuffle(all_possible_names)
unique_names = all_possible_names[:num_total]  # 300人分

# 部署内でのグループ分け（課分け）の関数
def partition(n):
    groups = []
    remaining = n
    while remaining > 0:
        if remaining <= 10:
            if len(groups) == 0:
                groups.append(remaining)
                remaining = 0
            else:
                if remaining < 3:
                    groups[-1] += remaining
                    remaining = 0
                else:
                    groups.append(remaining)
                    remaining = 0
        else:
            max_possible = min(10, remaining - 3)  # 次のグループに最低3名を確保
            group_size = random.randint(3, max_possible)
            groups.append(group_size)
            remaining -= group_size
    return groups

# 各部署ごとに人員を生成し、課に割り当てる
records = []
department_sections = {}  # 各部署の課リスト（例："営業": ["A課", "B課", ...]）
department_persons = {}   # 各部署に所属するレコードのインデックス

name_index = 0
current_index = 0
for dept, count in departments_info.items():
    groups = partition(count)
    # 万が一1グループしか生成されなかった場合は、無理やり2グループに分割
    if len(groups) == 1:
        g = groups[0]
        group1 = g // 2
        group2 = g - group1
        groups = [group1, group2]
    # グループ（課）名の生成：A課, B課, …
    sections = []
    for i in range(len(groups)):
        section_name = chr(65 + i) + "課"  # 65は"A"のASCIIコード
        sections.append(section_name)
    department_sections[dept] = sections
    department_persons[dept] = []
    # 各グループに対して人員を生成
    for i, group_size in enumerate(groups):
        for j in range(group_size):
            rec = {
                "氏名": unique_names[name_index],
                "部": dept,
                "課": sections[i],
                "旧部署": dept + sections[i]  # 例："営業A課"
            }
            name_index += 1
            records.append(rec)
            department_persons[dept].append(current_index)
            current_index += 1

# 異動処理：全体の10%（約30人）が異動
num_move_total = round(0.10 * num_total)
num_move_intra = round(0.07 * num_total)  # 同一部内で別の課へ異動（約21人）
num_move_inter = num_move_total - num_move_intra   # 別の部への異動（約9人）
all_indices = list(range(num_total))
random.shuffle(all_indices)
move_indices = all_indices[:num_move_total]
move_intra_indices = move_indices[:num_move_intra]
move_inter_indices = move_indices[num_move_intra:]

# 初期状態では旧部署と新部署が同一
for rec in records:
    rec["新部署"] = rec["旧部署"]

# 同一部内の課異動
for idx in move_intra_indices:
    rec = records[idx]
    dept = rec["部"]
    current_section = rec["課"]
    possible_sections = [s for s in department_sections[dept] if s != current_section]
    if possible_sections:
        new_section = random.choice(possible_sections)
        rec["新部署"] = dept + new_section

# 別部への異動
for idx in move_inter_indices:
    rec = records[idx]
    current_dept = rec["部"]
    possible_departments = [d for d in departments_info.keys() if d != current_dept]
    new_dept = random.choice(possible_departments)
    new_section = random.choice(department_sections[new_dept])
    rec["新部署"] = new_dept + new_section

# 新部署の本部（バックオフィス, 営業, 技術）を追加
for rec in records:
    new_dep = rec["新部署"]
    for dept in departments_info.keys():
        if new_dep.startswith(dept):
            rec["新部署の本部"] = dept
            break

# CSV出力
with open("personnel_movement.csv", "w", encoding="utf-8-sig", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["氏名", "旧部署", "新部署", "新部署の本部"])
    for rec in records:
        writer.writerow([rec["氏名"], rec["旧部署"], rec["新部署"], rec["新部署の本部"]])

print("CSVファイル 'personnel_movement.csv' が生成されました。")
```

## データ例

```
氏名,旧部署,新部署,新部署の本部
渡辺直子,バックオフィスA課,バックオフィスA課,バックオフィス
伊藤京子,バックオフィスA課,バックオフィスA課,バックオフィス
佐藤秀樹,バックオフィスA課,バックオフィスA課,バックオフィス
斎藤恵子,バックオフィスA課,バックオフィスA課,バックオフィス
田中直子,バックオフィスA課,バックオフィスA課,バックオフィス
佐藤恵,バックオフィスB課,バックオフィスB課,バックオフィス
松本恵子,バックオフィスB課,バックオフィスF課,バックオフィス
小林花子,バックオフィスB課,バックオフィスB課,バックオフィス
...
井上次郎,営業D課,営業D課,営業
佐藤真理子,営業D課,バックオフィスG課,バックオフィス
中村真理子,営業D課,営業D課,営業
...
```

## ネットワーク仕様

社内の人材のつながりは部署だけじゃなくてPJでのつながりや社内勉強会やクラブ活動、同期、個人的なつながり、など多数あるかと思います。また、つながりの程度も単に同じ部署で挨拶程度、ツーカーの中、同じスキル・お互いに補完するスキルでつながっている、またつながりの強さ等いろいろあります。
ただ今回はめっちゃ単純化して、

- 同じ課にいる人はつながりあり、他の課の人はつながりなし
- 人事異動で異動がなければ同じ課の人とのみのつながり
- 人事異動で異動があれば前の課と新しい課の両方に等しいつながり

言いたいことはわかります、そんな単純じゃ無いだろうって。でもそこで進まないんですよ。社内の人材のネットワーク図ってみたことあります？まず単純化してやりましょうよ。

また後述のとおり今回はGephiというツールでフォーマットはgdfを用いる。

https://gephi.org/users/supported-graph-formats/gdf-format/

これは変わったフォーマットで上部はノード（ネットワークの点ね）の情報、下部でエッジ（ネットワークの線、つながりね）の情報を示している。

このコードもChatGPTに作成してもらって（私が最初に試したときは自作したのですが）こんな感じ

```python
import csv
from collections import defaultdict

# CSV を読み込む
rows = []
with open("personnel_movement.csv", "r", encoding="utf-8-sig") as f:
    reader = csv.DictReader(f)
    for row in reader:
        rows.append(row)

# ノード情報の作成
# 各ノードは常に新部署の情報を使う
# 例：異動なしの場合 "山田花子,山田花子,バックオフィスA課,バックオフィス"
#      異動ありの場合 "鈴木太郎,鈴木太郎,営業B課,営業"
nodes = {}
for row in rows:
    name = row["氏名"]
    new_dep = row["新部署"]        # 例："バックオフィスA課" や "営業B課"
    dept_base = row["新部署の本部"]  # 例："バックオフィス", "営業", "技術"
    nodes[name] = {
        "name": name,
        "label": name,        # 常に氏名と同一
        "label2": new_dep,    # 新部署をそのまま使用
        "label3": dept_base   # 新部署の本部
    }

# グループ分け（エッジ生成用）
# 各人は、旧部署と新部署それぞれのグループに所属しているとする
group_memberships = defaultdict(set)
for row in rows:
    name = row["氏名"]
    old_group = row["旧部署"]  # 例："バックオフィスA課" や "営業D課"
    new_group = row["新部署"]
    # どちらかのグループで同じ人がいたらエッジ対象にするので両方に追加
    group_memberships[old_group].add(name)
    group_memberships[new_group].add(name)

# エッジ生成：各グループ内の全ペア（有向エッジ、自己ループ除く）
edges = set()
for group, members in group_memberships.items():
    members = list(members)
    for i in range(len(members)):
        for j in range(len(members)):
            if i != j:
                edges.add((members[i], members[j]))

# GDF ファイルの出力
with open("personnel_movement.gdf", "w", encoding="utf-8-sig", newline="") as f:
    # ノード定義ヘッダー
    f.write('nodedef>name VARCHAR,label VARCHAR,label2 VARCHAR,label3 VARCHAR\n')
    for name, attr in nodes.items():
        f.write(f'"{attr["name"]}","{attr["label"]}","{attr["label2"]}","{attr["label3"]}"\n')
    # エッジ定義ヘッダー
    f.write('edgedef>node1 VARCHAR,node2 VARCHAR\n')
    for src, dst in edges:
        f.write(f'"{src}","{dst}"\n')

print("GDF ファイル 'personnel_movement.gdf' が生成されました。")
```

## ネットワークデータ例

- ノード
    - name: 社員氏名
    - label: 社員氏名
    - label2: 課名
    - label3: 部名
- エッジ
    - node1, node2: 社員氏名

```
nodedef>name VARCHAR,label VARCHAR,label2 VARCHAR,label3 VARCHAR
"渡辺直子","渡辺直子","バックオフィスA課","バックオフィス"
"伊藤京子","伊藤京子","バックオフィスA課","バックオフィス"
"佐藤秀樹","佐藤秀樹","バックオフィスA課","バックオフィス"
"斎藤恵子","斎藤恵子","バックオフィスA課","バックオフィス"
"田中直子","田中直子","バックオフィスA課","バックオフィス"
"佐藤恵","佐藤恵","バックオフィスB課","バックオフィス"
"松本恵子","松本恵子","バックオフィスF課","バックオフィス"
...
"山本直樹","山本直樹","技術H課","技術"
"鈴木京子","鈴木京子","技術R課","技術"
edgedef>node1 VARCHAR,node2 VARCHAR
"木村弘","佐藤直樹"
"鈴木直樹","鈴木隆"
"鈴木直子","山本弘"
"小林花子","山口健一"
"田中恵子","松本太郎"
"小林恵子","井上秀樹"
```

## 可視化
参考資料のとおりいろいろなツールがありますが、今回はGephiを用います。WindowsでもMacでも簡単にインストールできると思います。

- Gephiを開く

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/dce86e76-3511-42c7-8b08-aedd59df4584.png)

- ファイル - 開く
    - 作成した personnel_movement.gdf を選択
    - 次の画面で「OK」
        - A -- B, B -- A と今回はエッジのデータを作成しているので同じのが2つあるよというワーニングが出るが今回は無視

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/b9f69636-5ce3-4f46-9ec8-289ea593340a.png)

これが社員のネットワーク図です！なのですがこれでは何もわからん、なのでみやすく表示を加工します。

- ノードの色付け
    - 左上の Appearance - Nodes - Partition - label3を選択
        - 部で色分けですね。label2課も選べます。
        ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/d187ece6-a1b9-4cd6-8f37-614786e7aac3.png)
    - 「適用」をクリック
        - ネットワークのノード（エッジにもつくみたいですが）に色がつきます。
        ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/4dab885a-7132-4fc7-ba4f-86861315de3c.png)
- ラベルの表示
    - 各ノードが社員なのでわかるように氏名を表示します
    - 画面下の
        - 「T」でラベル表示
        - フォント名とフォントサイズをクリックすると選択できるので
            - BoldをRegularに、サイズを16くらいで
         ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/2cb553b9-7ecd-4f9d-9320-d069f0bcc203.png)

        - Option: さらに右の ボードとレンチのアイコンをクリックすると表示するラベルを選択でき、
            - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/b7d3917b-84f7-4106-9698-d6ae4b3ea7e3.png)
            - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/f77ee91d-e14a-4d6d-8ad7-7e7137611ffb.png)
                - Id：氏名
                - Label：氏名（デフォルトで表示される）
                    - Idの氏名も表示されるので重複しますが、Labelは色付けに使えないようなのであえて重複しています）
                - label2：課名
                - label3：部名
    - これでラベルが表示されます（今回はLabel：氏名）
        ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/03ae9103-97ba-4e73-947a-96c5829258c5.png)
- レイアウト
    - ネットワーク表示のミソですね。いい感じに散らばってくれないと見にくいわけですがここが難しく色々なアルゴリズムが考えられています。今回は
    - レイアウト - Fruchterman Reingold を選択
    - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/036bc30c-d9ea-4576-a697-9a863c0a47fb.png)
    - 「実行」でネットワーク図が散らばっていきます
        - このとき**クリックしながら「解き放て」（&copy;『千と千尋の神隠し』ハク）と言うとテンションが上がります**

- ほらこの通り

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/296806b9-0cf4-4792-b85f-90512ee46739.png)

- ネットワーク操作方法
    - 共通
        - ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/116049/486b5906-184f-410e-a39b-09fcbb918cf5.png)
            - このスライドバーでエッジの太さが変わります
    - Mac
        - タッチパッド
            - ネットワークの拡大縮小
                - 二本指でドラッグ
            - ネットワークの移動
                - controlを押しながら一本指でドラッグ
    - Windows
        - 拡大縮小
            - ホイール
        - 移動
            - 左クリックでドラッグ
    - その他
        - [Gephi Tutorial Visualization (Japanese)](https://www.slideshare.net/slideshow/gephi-tutorialvisualization-ja/8490150)
        - 略しますが検索機能などもあります

# まとめ
そういうネットワーク仕様にしたので当然ながら
- 部単位（緑：バックオフィス、赤：営業、紫：技術）でサイロ化する
- 課によっては孤立、課によっては他の部、他の課とつながりができる
    - 役割によって孤立してても良い部署もあるでしょうし
    - ここ他の部署と交流がなきゃいけないのにないなとか
- 異動した人が（前の部署と新しい部署で等しく交流できていれば）Hubとなってつながりができる

どうですか。これを私は自社でやって軽く感動。こんなの見たことないもの。いや人材可視化ツールとかあるのは知ってますが、自社ので見たことありますか？やりましょうよ。

前提に戻りますが、これは非常に簡易化した「つながり」を示しています。他のデータが得られれば例えば、
- PJメンバー、勉強会、部活動等の情報でつながりを示す
    - 案件数などでつながりの重みを変える
- 役職や仕事の依頼関係によってつながりの重みや向きを変える
- ノードの属性を部署だけでなくスキルなども含めればPython人材が偏っているなとか

ネットワーク科学ってこれだけじゃなくて今回は単なる可視化で名刺がわり程度です。本当はライブラリなどを用いて、ネットワークの広さ、強さ、中心性（どれくらい重要か）、Hubは誰か、つながりの多い人少ない人、ある人とつながりを持つための最短経路、などの分析をすることが可能です。
あいつあちこちで喋ってるけど成果が上がんねーなとか、あの人地味でたまにしか喋んないけど意外と広いつながりを持ってるから成果が上がっているのかとか、そこまでいきたいですよね。





# 参考資料
|Original|Github|Star数|開発年|特長|
|---|---|--:|---|---|
|[Gephi](https://gephi.org/)|[gephi](https://github.com/gephi/gephi)|6.1k|2006|インタラクティブなネットワーク解析と視覚化が可能。<br>プラグインにより機能拡張が容易。<br/>オープンソースで多彩な拡張性を持つ。|
|[NetLogo](https://ccl.northwestern.edu/netlogo/)|[NetLogo](https://github.com/NetLogo/NetLogo)|1k|1999|直感的なシミュレーション環境で、エージェントベースのネットワークモデル構築が容易。<br/>『ネットワーク科学入門』で解説あり。|
|[Cytoscape](https://cytoscape.org/)|[cytoscape](https://github.com/cytoscape/cytoscape)|606|2002|生体分子ネットワーク解析の目的で開発され、バイオテクノロジー分野で進化したものの、最近は他分野の可視化にも用いられる。<br/>オリジナルサイトが充実しており、用途に合わせたプラグインが豊富。|
|[Tulip](https://tulip.labri.fr/site/)|[tulip](https://github.com/Tulip-Dev/tulip)|127|2008|大規模グラフの視覚化に特化し、豊富なレイアウトアルゴリズムを搭載。<br/>直感的な操作性と拡張可能なフレームワーク。|
|[Graph Commons](https://graphcommons.com/)|[Graph Commons](https://github.com/graphcommons)|-|2013|Webベースで簡単にネットワーク図の作成・共有が可能。|『ネットワーク科学』で作図例が紹介されている。<br/>『ネットワーク科学』で作図例が出ている|

## ライブラリ
- R, JavaScriptにも強力なライブラリがあるが、今回はPythonのみに絞る

|Original|Github|Star数|開発年|特長|
|---|---|--:|---|---|
|[NetworkX](https://networkx.org/)|[networkx](https://github.com/networkx/networkx)|15.5k|2005|純粋なPython実装：直感的で使いやすく、Pythonのエコシステム（numpy, pandas, matplotlibなど）と統合しやすい。<br/>豊富なアルゴリズム：グラフ理論に基づく多くの解析アルゴリズム（中心性、クラスタリング、パス探索など）が用意されており、学習リソースも充実している。<br/>プロトタイピングに最適：中小規模のネットワークなら、柔軟なコードで素早く試作できるが、純粋なPython実装のため、非常に大規模なグラフではパフォーマンスが課題になることがある。<br/>『データのつながりを活かす技術』『ネットワーク科学入門』で解説あり|
|[igraph](https://igraph.org/)|[igraph](https://github.com/igraph/igraph)|1.8k|2006|高速・効率的な実装：C言語でコア部分が実装されているため、大規模ネットワーク（数万～数十万ノード、数百万エッジ）でも高速に解析でき、メモリ効率も高い。<br/>多言語対応：Python、R、Cなど複数の言語で利用可能で、特にRユーザーや大規模解析を必要とするユーザーに好まれる。<br/>高度な解析機能：コミュニティ検出や中央性測定など、洗練されたアルゴリズムが多数搭載されている。ただし、APIはNetworkXに比べるとやや低レベルで、使いこなすにはある程度の学習が必要。|




- 備考：Star数は2025-03-20現在
    - それぞれオリジナルサイトからもダウンロードでき、Cytosocapeなどオリジナルサイトが充実しているものは使われているほどGitHubのStarが伸びない面はあるかも

## その他

|Original|Github|Star数|開発年|特長|備考|
|---|---|--:|---|---|---|
|[Graphviz](https://graphviz.org/)|[graphviz](https://github.com/graphp/graphviz)|291|1991|ネットワーク構造の記法。<br/>DOT言語を用いた記述で自動レイアウト生成が得意。<br/>多様な出力形式（PNG, SVG, PDF など）に対応。|


参考資料：

https://speakerdeck.com/likr/the-world-of-network-visualization

http://ipr-ctr.t.u-tokyo.ac.jp/jp/events/pdf/gsdm0121/tl.pdf

## 書籍
- バイブル
    - [ネットワーク科学: ひと・もの・ことの関係性をデータから解き明かす新しいアプローチ](https://amzn.to/4imFtBU)
- Python
    - [ネットワーク科学入門:Pythonで学ぶデータ分析とモデリング](https://amzn.to/4bHJuy8)
    - [データのつながりを活かす技術](https://amzn.to/4bJU2wU)
- R
    - [ネットワーク分析第2版](https://amzn.to/41EdGpk)