
Pythonでやってみた13(25/12/20更新):EDINET-APIで平均年収データ取得
1.概要
過去記事においてEDINETの有価証券報告書から平均年収を抽出して、比較・可視化しました。API仕様変更で動作しなくなったため、コード更新版を公開します。
せっかくなので一部改正しました。
会社:下記を使用
グラフ:棒グラフ以外も追加

2.環境構築
2-1.EDINET-API V2仕様確認
EDINETは「金融商品取引法に基づく有価証券報告書等の開示書類に関する電子開示システム」のことで、有価証券報告書、有価証券届出書、大量保有報告書等の開示書類について、その提出から公衆縦覧等に至るまでの一連の手続きを電子化するために開発されたシステムです。
「EDINET API関連資料」からEDINET API仕様書(Version 2)を確認します(ファイルだけダウンロードしておく)。
2-2.ポップアップブロックの許可
EDINET APIの利用に当たり、EDINETから発行されたAPIキーを使います。EDINETでAPIキーを発行する際に、ログイン認証が必要となります。
設定方法はEDINET API仕様書(Version 2)の2-2を参照し、ブラウザ側で設定します。

2-3.API KEY
EDINET API仕様書(Version 2)の「2-3 アカウントの作成と API キーの発行について」に従って、下記サイトからAPI KEYを発行します。

2-4.Pythonライブラリ
関連するPythonライブラリをインストールしておきます。
pip install requests pandas matplotlib pdfplumber japanize-matplotlib squarify3.コード作成
ベース作成
前回のコード+EDINET API仕様書(Version 2)のファイルをClaude Opus4.5に渡して、V2仕様にコードに修正しました。
※ChatGPT、Gemini、Manusでは一発で完成品は無理だった。
可視化の修正
棒グラフだけだとしょぼいので他のグラフも追加しました。
Github CopilotのAgent(Claude Opas4.5使用)で、可視化のコードを更新しました。
4.実装
全コード
ライブラリ読み込み
import os
import json
import time
import sqlite3
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional
import warnings
import requests
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from matplotlib.patches import Patch
import squarify # ツリーマップ用
# PDF解析
try:
import pdfplumber
PDF_ENGINE = "pdfplumber"
except ImportError:
import tabula
PDF_ENGINE = "tabula"
# 日本語フォント
try:
import japanize_matplotlib
except ImportError:
plt.rcParams['font.family'] = ['Hiragino Sans', 'Yu Gothic', 'Meiryo']
# 警告抑制
warnings.filterwarnings('ignore')
import urllib3
urllib3.disable_warnings()
print(f"✓ セットアップ完了 (PDF解析: {PDF_ENGINE})")設定(APIキーは手動で修正)
API_KEY = "APIキーいれてね" # EDINET APIキーを手入力
# 検索する企業名(部分一致で検索)
COMPANIES = [
# 商社・不動産・広告
'伊藤忠商事', '三井不動産', '電通', '三井物産', '東京海上日動火災保険',
'三菱商事', '丸紅', '野村證券', '住友商事', 'キーエンス', '双日', 'リクルート',
'豊田通商', '住友不動産', '商船三井', '三井住友海上火災保険', '東京ガス',
'東急不動産', '三菱地所', '大和ハウス工業', '東京建物',
# 製薬・化学
'第一三共', '武田薬品工業', '中外製薬', 'アステラス製薬', 'エーザイ',
'塩野義製薬', '旭化成', '富士フイルム', '三菱ケミカル', '住友化学',
'日東電工', '信越化学工業', '日本ペイント',
# 製造(電機・機械・自動車・鉄鋼)
'ファナック', 'トヨタ自動車', '日本製鉄', 'JFEスチール', '東京エレクトロン',
'日立製作所', 'ソニーグループ', 'AGC', 'デンソー', 'キヤノン', '小松製作所',
'三菱重工業', '豊田自動織機', '住友重機械工業', 'バンダイナムコホールディングス',
'本田技研工業', '川崎重工業', 'パナソニック', '三菱電機', '京セラ', 'NEC',
'富士電機', 'ニコン', 'オリンパス', 'リコー', '富士通', 'シャープ', 'マツダ',
'ヤマハ発動機', '村田製作所', 'アイシン', '日本ガイシ', 'TDK', 'オムロン',
'ニデック', 'SUBARU', 'スズキ', '三菱自動車', 'ダイハツ工業', 'いすゞ自動車',
'日野自動車', '安川電機', 'ミネベアミツミ',
# インフラ・建設・物流
'鹿島建設', '大林組', '清水建設', '積水ハウス', '大成建設', '長谷工コーポレーション',
'日揮ホールディングス', 'JR東海', 'JR東日本', 'JR西日本', '大阪ガス',
'関西電力', '中部電力', '東京電力', 'ANAホールディングス', '日本通運',
'小田急電鉄', '京王電鉄', '東武鉄道', '京成電鉄',
# 金融・通信・サービス
'明治安田生命', '第一生命', '三井住友銀行', '三菱UFJ銀行', 'みずほ銀行',
'あおぞら銀行', '千葉銀行', 'りそな銀行', '松井証券', 'NTTデータ',
'KDDI', 'NTTドコモ', 'ソフトバンク', 'スカパーJSAT', 'サイバーエージェント',
'大塚商会', 'NTTコミュニケーションズ', 'NTT東日本',
# 食品・その他
'キリンホールディングス', 'サントリー', 'アサヒグループホールディングス',
'明治', '味の素', 'JT', '資生堂', '花王', 'TOTO', 'ブリヂストン',
'任天堂', 'コナミグループ', 'イオン', 'セブン&アイ・ホールディングス'
]
# 検索期間
START_DATE = "2024-01-01" # 開始日
END_DATE = None # 終了日(Noneで今日まで)
# 色分け閾値(円)
THRESHOLD = 10_000_000 # 1000万円
# =============================================================================
print(f"検索企業数: {len(COMPANIES)}社")
print(f"検索期間: {START_DATE} 〜 {END_DATE or '今日'}")分析クラス定義
class EdinetSalaryAnalyzer:
"""EDINET 平均年収分析クラス"""
API_BASE = "https://api.edinet-fsa.go.jp/api/v2"
# セクター分類マッピング(企業名の部分一致で判定)
SECTOR_MAP = {
'テック': ['キーエンス', 'ソニー', '日立', '富士通', 'NEC', 'ファナック',
'オムロン', '村田製作所', 'TDK', 'パナソニック', '東芝', 'シャープ',
'キヤノン', 'リコー', 'エプソン', 'ブラザー', '京セラ', 'ローム',
'ルネサス', 'アドバンテスト', '東京エレクトロン', 'ディスコ'],
'自動車': ['トヨタ', 'ホンダ', '本田技研', '日産', 'マツダ', 'スバル',
'スズキ', 'ダイハツ', '三菱自動車', 'デンソー', 'アイシン',
'豊田自動織機', 'ジェイテクト', '住友電気', 'ブリヂストン'],
'製薬': ['武田薬品', '中外製薬', 'エーザイ', '塩野義', 'アステラス',
'大塚ホールディングス', '第一三共', '小野薬品', '協和キリン'],
'商社': ['三菱商事', '三井物産', '伊藤忠', '住友商事', '丸紅', '豊田通商', '双日'],
'金融': ['三菱UFJ', '三井住友', 'みずほ', '野村', '大和証券', '日本生命',
'第一生命', 'SOMPOホールディングス', 'MS&AD', '東京海上'],
'通信': ['NTT', 'KDDI', 'ソフトバンク', '楽天', 'LINE'],
'ゲーム・エンタメ': ['任天堂', 'ソニー・インタラクティブ', 'バンダイナムコ',
'スクウェア・エニックス', 'カプコン', 'コナミ', 'セガサミー'],
'素材・化学': ['信越化学', '三菱ケミカル', '住友化学', '旭化成', '東レ',
'帝人', 'クラレ', '日東電工', 'AGC'],
}
# セクター別カラーパレット
SECTOR_COLORS = {
'テック': '#3498db', # 青
'自動車': '#e74c3c', # 赤
'製薬': '#2ecc71', # 緑
'商社': '#f39c12', # オレンジ
'金融': '#9b59b6', # 紫
'通信': '#1abc9c', # ターコイズ
'ゲーム・エンタメ': '#e91e63', # ピンク
'素材・化学': '#795548', # ブラウン
'その他': '#95a5a6', # グレー
}
def __init__(self, api_key: str):
self.api_key = api_key
self.session = requests.Session()
self.session.verify = False
self.db_path = Path("database/salary_data.db")
self.output_dir = Path("output")
self._init_dirs()
self._init_db()
def _init_dirs(self):
self.db_path.parent.mkdir(exist_ok=True)
self.output_dir.mkdir(exist_ok=True)
def _get_sector(self, company_name: str) -> str:
"""企業名からセクターを判定(部分一致)"""
for sector, keywords in self.SECTOR_MAP.items():
for kw in keywords:
if kw in company_name:
return sector
return 'その他'
def _init_db(self):
with sqlite3.connect(self.db_path) as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS salary_data (
doc_id TEXT PRIMARY KEY,
company_name TEXT,
submit_date TEXT,
avg_salary INTEGER,
avg_age REAL,
employee_count INTEGER,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
def collect(self, companies: list, start_date: str, end_date: str = None):
"""データ収集"""
if end_date is None:
end_date = datetime.now().strftime("%Y-%m-%d")
dates = self._generate_dates(start_date, end_date)
collected = 0
print(f"📊 データ収集開始: {len(companies)}社, {len(dates)}日分")
for i, date in enumerate(dates):
if i % 30 == 0:
print(f" 処理中... {date} ({i+1}/{len(dates)})")
docs = self._get_docs(date)
for doc in docs:
# None対策: or '' で空文字に変換
doc_desc = doc.get('docDescription') or ''
if not doc_desc.startswith('有価証券報告書'):
continue
if '訂正' in doc_desc:
continue
# None対策: or '' で空文字に変換
company = doc.get('filerName') or ''
if not any(c in company for c in companies):
continue
doc_id = doc.get('docID')
if not doc_id:
continue
if self._exists(doc_id):
continue
result = self._process(doc)
if result and self._insert(result):
print(f" ✓ {company}: {result['avg_salary']:,}円")
collected += 1
time.sleep(1.0)
print(f"✅ 収集完了: {collected}件")
return collected
def _generate_dates(self, start: str, end: str) -> list:
start_dt = datetime.strptime(start, "%Y-%m-%d")
end_dt = datetime.strptime(end, "%Y-%m-%d")
dates = []
current = start_dt
while current <= end_dt:
dates.append(current.strftime("%Y-%m-%d"))
current += timedelta(days=1)
return dates
def _get_docs(self, date: str) -> list:
try:
params = {"date": date, "type": 2}
if self.api_key:
params["Subscription-Key"] = self.api_key
resp = self.session.get(f"{self.API_BASE}/documents.json", params=params, timeout=30)
data = resp.json()
return data.get('results') or []
except:
return []
def _process(self, doc: dict) -> Optional[dict]:
doc_id = doc.get('docID')
if not doc_id:
return None
pdf_path = self.output_dir / f"{doc_id}.pdf"
try:
# PDF取得
params = {"type": 2}
if self.api_key:
params["Subscription-Key"] = self.api_key
resp = self.session.get(
f"{self.API_BASE}/documents/{doc_id}",
params=params, timeout=60, stream=True
)
if resp.status_code != 200:
return None
with open(pdf_path, 'wb') as f:
for chunk in resp.iter_content(8192):
f.write(chunk)
# 給与抽出
salary_info = self._extract_salary(pdf_path)
if not salary_info:
return None
return {
'doc_id': doc_id,
'company_name': doc.get('filerName') or '',
'submit_date': (doc.get('submitDateTime') or '')[:10],
**salary_info
}
except Exception as e:
return None
finally:
if pdf_path.exists():
pdf_path.unlink()
def _extract_salary(self, pdf_path: Path) -> Optional[dict]:
if PDF_ENGINE == "pdfplumber":
return self._extract_pdfplumber(pdf_path)
else:
return self._extract_tabula(pdf_path)
def _extract_pdfplumber(self, pdf_path: Path) -> Optional[dict]:
try:
with pdfplumber.open(pdf_path) as pdf:
for page in pdf.pages:
tables = page.extract_tables()
if not tables:
continue
for table in tables:
result = self._parse_table(table)
if result:
return result
except:
pass
return None
def _extract_tabula(self, pdf_path: Path) -> Optional[dict]:
try:
dfs = tabula.read_pdf(str(pdf_path), pages='all', lattice=True)
for df in dfs:
if df.empty:
continue
table = [list(df.columns)] + df.values.tolist()
result = self._parse_table(table)
if result:
return result
except:
pass
return None
def _parse_table(self, table: list) -> Optional[dict]:
if not table:
return None
for i, row in enumerate(table):
if not row:
continue
row_text = ' '.join(str(c) for c in row if c)
if '平均年間給与' in row_text:
next_row = table[i+1] if i+1 < len(table) else None
return self._extract_from_row(row, next_row)
return None
def _extract_from_row(self, header: list, data: list) -> Optional[dict]:
result = {}
for j, h in enumerate(header):
if h is None:
continue
h = str(h)
val = str(data[j]) if data and j < len(data) and data[j] is not None else h
if '平均年間給与' in h:
salary = self._parse_salary(val, h)
if salary:
result['avg_salary'] = salary
elif '平均年齢' in h:
try:
age_str = ''.join(c for c in val if c.isdigit() or c == '.')
if age_str:
age = float(age_str)
if 18 <= age <= 70:
result['avg_age'] = age
except:
pass
elif '従業員数' in h:
try:
count_str = ''.join(c for c in val if c.isdigit())
if count_str:
result['employee_count'] = int(count_str)
except:
pass
return result if result.get('avg_salary') else None
def _parse_salary(self, value: str, header: str = "") -> Optional[int]:
try:
clean = ''.join(c for c in value.replace(',', '') if c.isdigit())
if not clean:
return None
salary = int(clean)
if '千' in header or '千' in value:
salary *= 1000
if 1_000_000 <= salary <= 50_000_000:
return salary
if salary < 100_000:
salary *= 1000
if 1_000_000 <= salary <= 50_000_000:
return salary
return None
except:
return None
def _exists(self, doc_id: str) -> bool:
if not doc_id:
return True
with sqlite3.connect(self.db_path) as conn:
r = conn.execute("SELECT 1 FROM salary_data WHERE doc_id = ?", (doc_id,)).fetchone()
return r is not None
def _insert(self, data: dict) -> bool:
try:
with sqlite3.connect(self.db_path) as conn:
conn.execute("""
INSERT OR IGNORE INTO salary_data
(doc_id, company_name, submit_date, avg_salary, avg_age, employee_count)
VALUES (?, ?, ?, ?, ?, ?)
""", (
data.get('doc_id'),
data.get('company_name'),
data.get('submit_date'),
data.get('avg_salary'),
data.get('avg_age'),
data.get('employee_count')
))
return conn.total_changes > 0
except:
return False
def get_data(self) -> pd.DataFrame:
"""最新データ取得"""
query = """
SELECT * FROM salary_data s1
WHERE submit_date = (
SELECT MAX(submit_date) FROM salary_data s2
WHERE s1.company_name = s2.company_name
)
ORDER BY avg_salary DESC
"""
with sqlite3.connect(self.db_path) as conn:
return pd.read_sql(query, conn)
def visualize(self, threshold: int = 10_000_000) -> pd.DataFrame:
"""可視化"""
df = self.get_data()
if df.empty:
print("データがありません。先にcollect()を実行してください。")
return df
fig, ax = plt.subplots(figsize=(14, max(8, len(df) * 0.4)), facecolor='white')
colors = ['#e74c3c' if s >= threshold else '#3498db' for s in df['avg_salary']]
bars = ax.barh(df['company_name'], df['avg_salary'], color=colors)
for bar, salary in zip(bars, df['avg_salary']):
ax.text(bar.get_width() + 100000, bar.get_y() + bar.get_height()/2,
f'{salary:,.0f}円', va='center', fontsize=9)
ax.set_xlabel('平均年間給与(円)', fontsize=12)
ax.set_title('企業別 平均年間給与ランキング', fontsize=14, fontweight='bold')
ax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f'{x/10000:.0f}万'))
ax.grid(axis='x', linestyle='--', alpha=0.7)
ax.invert_yaxis()
from matplotlib.patches import Patch
legend = [
Patch(facecolor='#e74c3c', label=f'{threshold/10000:.0f}万円以上'),
Patch(facecolor='#3498db', label=f'{threshold/10000:.0f}万円未満')
]
ax.legend(handles=legend, loc='lower right')
plt.tight_layout()
plt.savefig('salary_chart.png', dpi=150, bbox_inches='tight')
plt.show()
return df
def show_ranking(self, top_n: int = 20):
"""ランキング表示"""
df = self.get_data()
if df.empty:
print("データがありません。先にcollect()を実行してください。")
return
print("\n" + "="*60)
print(" 📊 平均年収ランキング")
print("="*60)
for idx, (i, row) in enumerate(df.head(top_n).iterrows()):
salary = row['avg_salary']
company = row['company_name'][:25]
print(f" {idx+1:2}. {company:<27} {salary:>12,}円")
print("-"*60)
print(f" 平均: {df['avg_salary'].mean():,.0f}円")
print(f" 中央値: {df['avg_salary'].median():,.0f}円")
print(f" データ件数: {len(df)}社")
print("="*60)
# =========================================================================
# 100社以上対応の可視化メソッド群
# =========================================================================
def visualize_boxplot(self) -> pd.DataFrame:
"""セクター別 箱ひげ図:分布の傾向を一目で把握"""
df = self.get_data()
if df.empty:
print("データがありません。先にcollect()を実行してください。")
return df
# セクター列を追加
df['sector'] = df['company_name'].apply(self._get_sector)
# セクター別の中央値でソート
sector_order = df.groupby('sector')['avg_salary'].median().sort_values(ascending=False).index.tolist()
fig, ax = plt.subplots(figsize=(14, 8), facecolor='white')
# 箱ひげ図データ準備
box_data = [df[df['sector'] == s]['avg_salary'].values / 10000 for s in sector_order]
colors = [self.SECTOR_COLORS.get(s, '#95a5a6') for s in sector_order]
# 箱ひげ図描画
bp = ax.boxplot(box_data, labels=sector_order, patch_artist=True, vert=True)
for patch, color in zip(bp['boxes'], colors):
patch.set_facecolor(color)
patch.set_alpha(0.7)
# 外れ値(高年収企業)にラベル表示
for i, sector in enumerate(sector_order):
sector_df = df[df['sector'] == sector]
q3 = sector_df['avg_salary'].quantile(0.75)
iqr = sector_df['avg_salary'].quantile(0.75) - sector_df['avg_salary'].quantile(0.25)
threshold = q3 + 1.5 * iqr
outliers = sector_df[sector_df['avg_salary'] > threshold]
for _, row in outliers.iterrows():
ax.annotate(row['company_name'][:10],
(i + 1, row['avg_salary'] / 10000),
fontsize=8, ha='left', va='bottom',
xytext=(5, 0), textcoords='offset points')
ax.set_ylabel('平均年間給与(万円)', fontsize=12)
ax.set_xlabel('セクター', fontsize=12)
ax.set_title('セクター別 平均年収分布(箱ひげ図)', fontsize=14, fontweight='bold')
ax.grid(axis='y', linestyle='--', alpha=0.5)
plt.xticks(rotation=30, ha='right')
plt.tight_layout()
plt.savefig('salary_boxplot.png', dpi=150, bbox_inches='tight')
plt.show()
return df
def visualize_treemap(self) -> pd.DataFrame:
"""ツリーマップ:100社以上を一目で俯瞰、面積=給与"""
df = self.get_data()
if df.empty:
print("データがありません。先にcollect()を実行してください。")
return df
df['sector'] = df['company_name'].apply(self._get_sector)
df['salary_man'] = df['avg_salary'] / 10000 # 万円単位
fig, ax = plt.subplots(figsize=(16, 10), facecolor='white')
# ラベル作成(企業名 + 給与)
labels = [f"{row['company_name'][:8]}\n{row['salary_man']:.0f}万"
for _, row in df.iterrows()]
colors = [self.SECTOR_COLORS.get(self._get_sector(name), '#95a5a6')
for name in df['company_name']]
# ツリーマップ描画
squarify.plot(sizes=df['salary_man'], label=labels, color=colors,
alpha=0.8, ax=ax, text_kwargs={'fontsize': 8})
ax.axis('off')
ax.set_title('企業別 平均年収ツリーマップ(面積=給与額)', fontsize=14, fontweight='bold')
# 凡例
legend_handles = [Patch(facecolor=color, label=sector)
for sector, color in self.SECTOR_COLORS.items()
if sector in df['sector'].unique()]
ax.legend(handles=legend_handles, loc='upper left', bbox_to_anchor=(1.01, 1),
title='セクター', fontsize=9)
plt.tight_layout()
plt.savefig('salary_treemap.png', dpi=150, bbox_inches='tight')
plt.show()
return df
def visualize_scatter(self) -> pd.DataFrame:
"""散布図:年齢×給与×従業員数の3変数同時表示"""
df = self.get_data()
if df.empty:
print("データがありません。先にcollect()を実行してください。")
return df
df['sector'] = df['company_name'].apply(self._get_sector)
# 欠損値除去
plot_df = df.dropna(subset=['avg_age', 'avg_salary'])
fig, ax = plt.subplots(figsize=(14, 10), facecolor='white')
# セクターごとにプロット
for sector in plot_df['sector'].unique():
sdf = plot_df[plot_df['sector'] == sector]
color = self.SECTOR_COLORS.get(sector, '#95a5a6')
# バブルサイズ(従業員数、欠損は固定サイズ)
sizes = sdf['employee_count'].fillna(500) / 50
sizes = sizes.clip(50, 500) # 50〜500の範囲に制限
ax.scatter(sdf['avg_age'], sdf['avg_salary'] / 10000,
s=sizes, c=color, alpha=0.6, label=sector, edgecolors='white')
# 高年収企業にラベル表示(上位10社)
top10 = plot_df.nlargest(10, 'avg_salary')
for _, row in top10.iterrows():
ax.annotate(row['company_name'][:10],
(row['avg_age'], row['avg_salary'] / 10000),
fontsize=9, ha='left', va='bottom',
xytext=(5, 5), textcoords='offset points',
bbox=dict(boxstyle='round,pad=0.3', facecolor='yellow', alpha=0.5))
ax.set_xlabel('平均年齢(歳)', fontsize=12)
ax.set_ylabel('平均年間給与(万円)', fontsize=12)
ax.set_title('年齢 × 給与 × 従業員数(バブルサイズ)', fontsize=14, fontweight='bold')
ax.grid(True, linestyle='--', alpha=0.5)
ax.legend(loc='upper left', title='セクター')
plt.tight_layout()
plt.savefig('salary_scatter.png', dpi=150, bbox_inches='tight')
plt.show()
return df
def visualize_dashboard(self, top_n: int = 20, threshold: int = 10_000_000) -> pd.DataFrame:
"""統合ダッシュボード:4種グラフを2×2で一覧表示"""
df = self.get_data()
if df.empty:
print("データがありません。先にcollect()を実行してください。")
return df
df['sector'] = df['company_name'].apply(self._get_sector)
df['salary_man'] = df['avg_salary'] / 10000
fig, axes = plt.subplots(2, 2, figsize=(20, 16), facecolor='white')
fig.suptitle(f'📊 企業別 平均年収ダッシュボード({len(df)}社)', fontsize=18, fontweight='bold', y=0.98)
# ===== 1. 棒グラフ Top N(左上)=====
ax1 = axes[0, 0]
top_df = df.head(top_n)
colors1 = [self.SECTOR_COLORS.get(self._get_sector(name), '#95a5a6')
for name in top_df['company_name']]
bars = ax1.barh(top_df['company_name'], top_df['salary_man'], color=colors1)
for bar, salary in zip(bars, top_df['salary_man']):
ax1.text(bar.get_width() + 10, bar.get_y() + bar.get_height()/2,
f'{salary:.0f}万', va='center', fontsize=9)
ax1.set_xlabel('平均年間給与(万円)', fontsize=11)
ax1.set_title(f'年収ランキング Top {top_n}', fontsize=13, fontweight='bold')
ax1.grid(axis='x', linestyle='--', alpha=0.5)
ax1.invert_yaxis()
# ===== 2. 箱ひげ図(右上)=====
ax2 = axes[0, 1]
sector_order = df.groupby('sector')['avg_salary'].median().sort_values(ascending=False).index.tolist()
box_data = [df[df['sector'] == s]['salary_man'].values for s in sector_order]
colors2 = [self.SECTOR_COLORS.get(s, '#95a5a6') for s in sector_order]
bp = ax2.boxplot(box_data, labels=sector_order, patch_artist=True, vert=True)
for patch, color in zip(bp['boxes'], colors2):
patch.set_facecolor(color)
patch.set_alpha(0.7)
ax2.set_ylabel('平均年間給与(万円)', fontsize=11)
ax2.set_title('セクター別 年収分布', fontsize=13, fontweight='bold')
ax2.grid(axis='y', linestyle='--', alpha=0.5)
plt.sca(ax2)
plt.xticks(rotation=30, ha='right')
# ===== 3. ツリーマップ(左下)=====
ax3 = axes[1, 0]
labels = [f"{row['company_name'][:6]}\n{row['salary_man']:.0f}万"
for _, row in df.iterrows()]
colors3 = [self.SECTOR_COLORS.get(self._get_sector(name), '#95a5a6')
for name in df['company_name']]
squarify.plot(sizes=df['salary_man'], label=labels, color=colors3,
alpha=0.8, ax=ax3, text_kwargs={'fontsize': 7})
ax3.axis('off')
ax3.set_title('全社俯瞰ツリーマップ(面積=給与額)', fontsize=13, fontweight='bold')
# ===== 4. 散布図(右下)=====
ax4 = axes[1, 1]
plot_df = df.dropna(subset=['avg_age'])
for sector in plot_df['sector'].unique():
sdf = plot_df[plot_df['sector'] == sector]
color = self.SECTOR_COLORS.get(sector, '#95a5a6')
sizes = sdf['employee_count'].fillna(500) / 80
sizes = sizes.clip(30, 300)
ax4.scatter(sdf['avg_age'], sdf['salary_man'],
s=sizes, c=color, alpha=0.6, label=sector, edgecolors='white')
# 上位5社にラベル
for _, row in plot_df.nlargest(5, 'avg_salary').iterrows():
ax4.annotate(row['company_name'][:8],
(row['avg_age'], row['salary_man']),
fontsize=8, ha='left', va='bottom',
xytext=(3, 3), textcoords='offset points',
bbox=dict(boxstyle='round,pad=0.2', facecolor='yellow', alpha=0.5))
ax4.set_xlabel('平均年齢(歳)', fontsize=11)
ax4.set_ylabel('平均年間給与(万円)', fontsize=11)
ax4.set_title('年齢 × 給与(バブルサイズ=従業員数)', fontsize=13, fontweight='bold')
ax4.grid(True, linestyle='--', alpha=0.5)
ax4.legend(loc='upper left', fontsize=8, title='セクター')
# 共通凡例
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.savefig('salary_dashboard.png', dpi=150, bbox_inches='tight')
plt.show()
return df
# インスタンス作成
analyzer = EdinetSalaryAnalyzer(API_KEY)
print("✓ 分析エンジン準備完了")実行
# メイン処理
analyzer.collect(COMPANIES, START_DATE, END_DATE) ## データ収集可視化
# テキストでランキング表示
analyzer.show_ranking(top_n=30)
# ===== 4種グラフを2×2ダッシュボードで一覧表示 =====
df = analyzer.visualize_dashboard(top_n=20)5.結果
OUTPUT
結果は以下の通り
[OUT]
============================================================
📊 平均年収ランキング
============================================================
1. 株式会社キーエンス 20,391,138円
2. 三菱商事株式会社 20,333,662円
3. 三井物産株式会社 19,964,000円
4. 伊藤忠商事株式会社 18,045,578円
5. 三井不動産株式会社 17,562,000円
6. 住友商事株式会社 17,443,137円
7. 丸紅株式会社 17,087,936円
8. 株式会社商船三井 16,614,139円
9. 株式会社電通グループ 15,075,028円
10. 株式会社 商船三井 14,293,649円
11. ソフトバンクグループ株式会社 13,631,161円
12. 東京エレクトロン株式会社 13,543,475円
13. 三菱地所株式会社 13,478,300円
14. 豊田通商株式会社 13,202,952円
15. 東急不動産ホールディングス株式会社 12,784,000円
16. 双日株式会社 12,742,594円
17. サントリーホールディングス株式会社 12,219,305円
18. アサヒグループホールディングス株式会社 12,181,758円
19. 株式会社バンダイナムコホールディングス 12,161,000円
20. 中外製薬株式会社 12,073,828円
21. 鹿島建設株式会社 11,847,369円
22. ファナック株式会社 11,639,000円
23. サントリー食品インターナショナル株式会社 11,611,682円
24. 株式会社大林組 11,404,238円
25. 富士フイルムホールディングス株式会社 11,242,845円
26. 株式会社電通総研 11,229,000円
27. ソニーグループ株式会社 11,183,744円
28. 第一三共株式会社 11,142,879円
29. 東京建物株式会社 11,108,000円
30. 武田薬品工業株式会社 11,038,000円
------------------------------------------------------------
平均: 9,588,759円
中央値: 8,790,500円
データ件数: 128社
============================================================





結果の解析
まず給与がよさそうなところを抽出しているので数値が全体を表していないことは注意。
ホールディングスの年収はあてにならないので従業員数との関係を取ろうと思ったけど、数値をちょっといじる必要あったのであきらめました。
そのうえで製造業の上はキーエンスを除くといまいちですね。
参考資料
あとがき
どっかでもっと使いやすいように更新するかも