PythonとWorld Bankデータで世界の産業構造を可視化する方法

はじめに 世界の国々の経済は、どのように変化しているのでしょうか?今回は、World Bank(世界銀行)が提供するオープンデータを使って、各国の産業構造の変化を可視化してみました。 この記事では、Pythonのpandasとmatplotlibを使って、1997年から2024年までの約30年間の産業構造の変化をグラフにする方法を紹介します。 産業構造とは? 経済学では、産業を3つに分類します: 第一次産業:農業、林業、漁業など(自然から直接資源を得る産業) 第二次産業:製造業、建設業など(原材料を加工する産業) 第三次産業:サービス業、金融、小売など(形のないサービスを提供する産業) 国が経済発展すると、第一次産業から第二次産業へ、そして第三次産業へとシフトしていく傾向があります。これを「産業構造の高度化」と呼びます。 使用したデータ World Bankが提供している以下のデータを使用しました: 第一次産業: Agriculture, forestry, and fishing, value added (% of GDP) 第二次産業: Industry (including construction), value added (% of GDP) 第三次産業: Services, value added (% of GDP) これらは各産業がGDP(国内総生産)に占める割合を示しています。 分析対象国 今回は、経済発展段階や地域が異なる10カ国を選びました: 日本(JPN): 先進国・アジア 中国(CHN): 新興国・急成長 アメリカ(USA): 先進国・北米 ドイツ(DEU): 先進国・欧州 インド(IND): 新興国・南アジア 韓国(KOR): 先進国・アジア インドネシア(IDN): 新興国・東南アジア ベトナム(VNM): 新興国・急成長 シンガポール(SGP): 先進国・都市国家 タイ(THA): 新興国・東南アジア ポーランド(POL): 中所得国・欧州 Pythonコード 以下が実際に使用したコードです。 # 第一次: https://data.worldbank.org/indicator/NV.AGR.TOTL.ZS # 第二次: https://data.worldbank.org/indicator/NV.IND.TOTL.ZS # 第三次: https://data.worldbank.org/indicator/NV.SRV.TOTL.ZS # 産業付加価値GDP import pandas as pd import matplotlib.pyplot as plt # CSVデータ取得 df_1 = pd.read_csv('1.csv', skiprows=3) df_2 = pd.read_csv('2.csv', skiprows=3) df_3 = pd.read_csv('3.csv', skiprows=3) # 国名のマッピング country_names = { 'JPN': 'Japan', 'CHN': 'China', 'USA': 'United States', 'DEU': 'Germany', 'IND': 'India', 'KOR': 'South Korea', 'IDN': 'Indonesia', 'VNM': 'Vietnam', 'SGP': 'Singapore', 'THA': 'Thailand', 'POL': 'Poland', } def to_chart(df, codes, begin, end, title, filename): plt.figure(figsize=(14, 9)) for code in codes: data = df[df['Country Code'] == code] years = [str(year) for year in range(begin, end)] dict_data = data[years].iloc[0].to_dict() years = list(dict_data.keys()) values = list(dict_data.values()) line = plt.plot(years, values, marker='o', linewidth=2, markersize=4) # 線の最初(スタート地点)に国名ラベルを表示 plt.text(years[0], values[0], f'{country_names.get(code, code)} ', verticalalignment='center', horizontalalignment='right', fontsize=9, color=line[0].get_color(), fontweight='bold') plt.xlabel('Year', fontsize=11) plt.ylabel('Value (%)', fontsize=11) plt.title(f'{title} ({begin}-{end})', fontsize=13) plt.grid(True, alpha=0.3) plt.xticks(rotation=45) plt.tight_layout() # 画像として保存 plt.savefig(filename, dpi=300, bbox_inches='tight') plt.close() # 3つのグラフを生成 to_chart(df_1, list(country_names.keys()), 1997, 2024, "第一次産業 (Agriculture, forestry, and fishing)", "chart_primary.png") to_chart(df_2, list(country_names.keys()), 1997, 2024, "第二次産業 (Industry including construction)", "chart_secondary.png") to_chart(df_3, list(country_names.keys()), 1997, 2024, "第三次産業 (Services)", "chart_tertiary.png") 分析結果 第一次産業(農業・林業・漁業) ...

January 26, 2026 · 2 min

ラズパイ6台で作る、絶対に止まらない最強の自宅ネットワーク冗長化計画

はじめに 「自宅サーバーを構築したが、1台落ちただけで家族全員のネットが止まった」 そんな苦い経験(特にDNS/DHCP周りでの同期失敗)を経て、今回はRaspberry Pi 6台(+α)を駆使した「高可用性(HA)」に特化した自宅ネットワークを再設計します。 今回のコンセプトは「速度よりも、止まらないこと」。 北欧神話の神々の名を冠した6台のラズパイによる、3層の冗長化レイヤーを構築します。 1. ネットワーク全体像 上位ルーターとはWi-Fiで接続し、内部ネットワークは有線L2スイッチを中心に構成します。物理的に役割を分離することで、障害時の原因切り分けを容易にしています。 階層化の設計案 Edge層 (L1): インターネットへの門番。KeepalivedでゲートウェイIPを共有。 Core層 (L2): DHCPやDNS、認証など、NWの頭脳となる機能を同期。 Service層 (L3): ファイルサーバーなどの実データをレプリケーションして保持。 2. サーバー構成表:北欧神話の神々 物理筐体 ホスト名 役割 冗長化の仕組み Pi 3 (A) Odin 主系Gateway / VPN Keepalived (VIP: 192.168.1.1) Pi 3 (B) Frigg 副系Gateway / VPN Odinと仮想IPを共有 Pi 3 (C) Huginn DNS / DHCP Primary ISC-DHCP Failover / Gravity Sync Pi 3 (D) Muninn DNS / DHCP Secondary Huginnとリアルタイム同期 Pi 3 (E) Mjolnir ストレージ (NAS) GlusterFS + Keepalived (VIP: .200) Pi 3 (F) Gungnir ストレージ (NAS) Mjolnirとデータレプリケーション 3. 過去の失敗を防ぐ「守り」の技術選定 ① DNS/DHCP:仮想IPに頼らない 過去、DNSやDHCPを仮想IP(Keepalived)で制御しようとして失敗した経験から、今回はプロトコル標準の冗長化を採用します。 ...

January 24, 2026 · 1 min

データが暴く物価高騰の真実 - エネルギー価格と為替の相関分析で見えた意外な結論

はじめに - 「円安=物価高」という通説への挑戦 「円安だから物価が上がる」――ニュースで繰り返されるこのフレーズ。本当にそうなのか?統計総局の消費者物価指数(CPI)と為替レートのデータを使って、この仮説を検証してみた。 データ準備 使用データ 消費者物価指数:統計総局『tmi2020a.csv』(2020年基準) 為替レート:みずほ銀行『quote.csv』(日次データを月次平均化) 期間:2023年1月〜2026年1月(3年間) 前処理 import pandas as pd import matplotlib.pyplot as plt from scipy.stats import linregress # CPI読み込み(ヘッダー5行スキップ) cpi_df = pd.read_csv('./tmi/tmi2020a.csv') cpi_clean = cpi_df.iloc[5:].copy().reset_index(drop=True) cpi_clean['エネルギー'] = pd.to_numeric(cpi_clean['エネルギー'], errors='coerce') # 為替読み込み(日次→月次平均) fx_df = pd.read_csv('./doru/quote.csv', encoding='utf-8') fx_clean = fx_df.iloc[2:].copy() fx_clean['日付'] = pd.to_datetime(fx_clean.iloc[:, 0], format='%Y/%m/%d') fx_clean['USD'] = pd.to_numeric(fx_clean.iloc[:, 1], errors='coerce') fx_clean['年月'] = fx_clean['日付'].dt.strftime('%Y%m') monthly_fx = fx_clean.groupby('年月')['USD'].mean().reset_index() monthly_fx.columns = ['年月', 'ドル円'] # データ結合 recent = cpi_clean[cpi_clean['類・品目'] >= '202301'].copy() data = recent.merge(monthly_fx, left_on='類・品目', right_on='年月', how='left') まず全体像を把握する グラフから見える3つの真実 1. エネルギー価格の激しい変動 オレンジ線を見ると、2023年初頭の135から2023年秋には104まで急落(-23%)。その後も上下を繰り返し、最終的に122で着地。地政学リスクがそのまま価格に反映されている。 2. 食料価格の不可逆的上昇 ピンク線は2023年から2025年にかけてほぼ一直線に上昇(109→127、+16%)。一度上がった食品価格は下がらない構造的問題が見える。 3. 総合指数の「マイルド感」 青線は安定的に上昇(104→112、+7%)。しかし国民が実感する物価高は、日常的に買う食料品の16%上昇の方。統計と実感の乖離がここに現れている。 ...

January 23, 2026 · 2 min

K3s実験環境構築マニュアル:安全に実験→破壊→復元のサイクルを回す

Proxmox上のK3s環境で安全に実験・破壊・復元サイクルを回すための完全ガイド

January 19, 2026 · 4 min

LeetCode 735: Asteroid Collision - スタックで衝突判定を美しく解く

問題概要 整数で表される小惑星の配列 asteroids が与えられる。各小惑星について: 絶対値:大きさ 符号:方向(正=右、負=左) 全て同じ速度で移動 衝突ルール: 小さい方が爆発 同じ大きさなら両方爆発 同じ方向に移動する小惑星は衝突しない 全ての衝突後の状態を返せ。 失敗した実装 from collections import deque class Solution: def asteroidCollision(self, asteroids: List[int]) -> List[int]: stack = [] for aster in asteroids: if len(stack) <= 0 or (aster <= 0) == (stack[-1] <= 0): stack.append(aster) else: while len(stack) > 0 and (aster <= 0) != (stack[-1] <= 0) and abs(aster) > abs(stack[-1]): stack.pop() if len(stack) <= 0 or (stack[-1] <= 0) == (aster <= 0): stack.append(aster) elif abs(aster) == abs(stack[-1]): stack.pop() return stack 問題点 同じ条件判定 if len(stack) <= 0 or (stack[-1] <= 0) == (aster <= 0) が2箇所に重複 制御フローが複雑で読みにくい (aster <= 0) != (stack[-1] <= 0) は「符号が異なる」を検出するが、衝突しないケースも含む 最適解 class Solution: def asteroidCollision(self, asteroids: List[int]) -> List[int]: stack = [] for asteroid in asteroids: while stack and asteroid < 0 < stack[-1]: # 右向き vs 左向きの衝突が発生 if abs(stack[-1]) < abs(asteroid): # 右向きが小さい → 爆発して次の右向きとも衝突判定 stack.pop() continue elif abs(stack[-1]) == abs(asteroid): # 同じ大きさ → 両方爆発 stack.pop() break else: # 衝突しなかった or 左向きが勝った stack.append(asteroid) return stack わかったこと 1. ループ条件の本質 asteroid < 0 < stack[-1] これは (asteroid < 0) and (0 < stack[-1]) と同じ。つまり: ...

January 13, 2026 · 2 min

Go言語でネットワークプログラミングを学ぶ - 第2章

2.1 プロジェクト構造 go-network-programming/ ├── go.mod ├── go.sum ├── main.go ├── packet.go ├── node.go ├── link.go ├── network_stats.go # 新規追加 └── bandwidth_limiter.go # 新規追加 この章では、ネットワークに時間の概念を本格的に導入します。実際のネットワークのように、帯域幅制限、パケット処理時間、スループット測定を実装し、大きなファイルの送信をシミュレートします。 2.2 ネットワーク統計の追加 ネットワークの性能を測定するための統計機能を追加します。 ファイル名: ./network_stats.go package main import ( "fmt" "sync" "time" ) // NetworkStats はネットワークの統計情報を管理する // 実際のネットワークモニタリングツールのような機能を提供 type NetworkStats struct { mu sync.RWMutex startTime time.Time totalPacketsSent int64 totalPacketsRecv int64 totalBytesSent int64 totalBytesRecv int64 packetLossCount int64 lastUpdateTime time.Time } // NewNetworkStats は新しい統計オブジェクトを作成 func NewNetworkStats() *NetworkStats { return &NetworkStats{ startTime: time.Now(), lastUpdateTime: time.Now(), } } // RecordSentPacket は送信パケットを記録 func (ns *NetworkStats) RecordSentPacket(packet *Packet) { ns.mu.Lock() defer ns.mu.Unlock() ns.totalPacketsSent++ ns.totalBytesSent += int64(packet.Size) ns.lastUpdateTime = time.Now() } // RecordReceivedPacket は受信パケットを記録 func (ns *NetworkStats) RecordReceivedPacket(packet *Packet) { ns.mu.Lock() defer ns.mu.Unlock() ns.totalPacketsRecv++ ns.totalBytesRecv += int64(packet.Size) ns.lastUpdateTime = time.Now() } // RecordPacketLoss はパケット損失を記録 func (ns *NetworkStats) RecordPacketLoss() { ns.mu.Lock() defer ns.mu.Unlock() ns.packetLossCount++ ns.lastUpdateTime = time.Now() } // GetThroughput は現在のスループットを計算(bps: bits per second) func (ns *NetworkStats) GetThroughput() float64 { ns.mu.RLock() defer ns.mu.RUnlock() duration := time.Since(ns.startTime).Seconds() if duration == 0 { return 0 } // バイト数をビット数に変換(1バイト = 8ビット) totalBits := float64(ns.totalBytesSent) * 8 return totalBits / duration } // GetPacketLossRate はパケット損失率を計算(0.0-1.0) func (ns *NetworkStats) GetPacketLossRate() float64 { ns.mu.RLock() defer ns.mu.RUnlock() if ns.totalPacketsSent == 0 { return 0.0 } return float64(ns.packetLossCount) / float64(ns.totalPacketsSent) } // Print は統計情報を表示 func (ns *NetworkStats) Print() { ns.mu.RLock() defer ns.mu.RUnlock() duration := time.Since(ns.startTime) throughputBps := ns.GetThroughput() throughputKbps := throughputBps / 1000 lossRate := ns.GetPacketLossRate() * 100 fmt.Printf("=== Network Statistics ===\n") fmt.Printf("Duration: %v\n", duration.Round(time.Millisecond)) fmt.Printf("Packets Sent: %d\n", ns.totalPacketsSent) fmt.Printf("Packets Received: %d\n", ns.totalPacketsRecv) fmt.Printf("Bytes Sent: %d\n", ns.totalBytesSent) fmt.Printf("Bytes Received: %d\n", ns.totalBytesRecv) fmt.Printf("Throughput: %.2f Kbps\n", throughputKbps) fmt.Printf("Packet Loss Rate: %.2f%%\n", lossRate) fmt.Printf("=========================\n") } 2.3 帯域幅制限機能の実装 実際のネットワークのように、帯域幅制限を実装します。 ...

January 11, 2026 · 7 min

Go言語でネットワークプログラミングを学ぶ - 第3章

3.1 プロジェクト構造 go-network-programming/ ├── go.mod ├── go.sum ├── main.go ├── packet.go ├── node.go ├── link.go ├── network_stats.go ├── bandwidth_limiter.go ├── mac_address.go # 新規追加 ├── ethernet_frame.go # 新規追加 └── switch.go # 新規追加 この章では、スイッチを実装して複数のノードを接続できるローカルネットワークを構築します。また、MACアドレスを導入してイーサネットレベルでの通信を実現します。 3.2 MACアドレスの実装 MACアドレス(Media Access Control Address)は、ネットワークインターフェースの物理アドレスです。 ファイル名: ./mac_address.go package main import ( "fmt" "math/rand" "strconv" "strings" ) // MACAddress はMAC(Media Access Control)アドレスを表現する // 実際のイーサネットで使用される6バイトの物理アドレス type MACAddress struct { bytes [6]byte // 6バイトのMACAアドレス(例:aa:bb:cc:dd:ee:ff) } // NewMACAddress は指定されたバイト配列からMACアドレスを作成 func NewMACAddress(bytes [6]byte) MACAddress { return MACAddress{bytes: bytes} } // ParseMACAddress は文字列からMACアドレスを解析 // 例:ParseMACAddress("aa:bb:cc:dd:ee:ff") func ParseMACAddress(s string) (MACAddress, error) { parts := strings.Split(s, ":") if len(parts) != 6 { return MACAddress{}, fmt.Errorf("invalid MAC address format: %s", s) } var mac MACAddress for i, part := range parts { val, err := strconv.ParseUint(part, 16, 8) if err != nil { return MACAddress{}, fmt.Errorf("invalid hex value in MAC address: %s", part) } mac.bytes[i] = byte(val) } return mac, nil } // RandomMACAddress はランダムなMACアドレスを生成 // ユニキャスト、ローカル管理アドレスとして生成 func RandomMACAddress() MACAddress { var mac MACAddress for i := 0; i < 6; i++ { mac.bytes[i] = byte(rand.Intn(256)) } // ユニキャスト(LSBを0に)、ローカル管理(2番目のLSBを1に)に設定 mac.bytes[0] = (mac.bytes[0] & 0xFC) | 0x02 return mac } // String はMACアドレスの文字列表現を返す func (mac MACAddress) String() string { return fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", mac.bytes[0], mac.bytes[1], mac.bytes[2], mac.bytes[3], mac.bytes[4], mac.bytes[5]) } // Equals は2つのMACアドレスが等しいかチェック func (mac MACAddress) Equals(other MACAddress) bool { return mac.bytes == other.bytes } // IsUnicast はユニキャストアドレスかチェック func (mac MACAddress) IsUnicast() bool { return (mac.bytes[0] & 0x01) == 0 } // IsBroadcast はブロードキャストアドレスかチェック func (mac MACAddress) IsBroadcast() bool { return mac.bytes == [6]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} } // IsMulticast はマルチキャストアドレスかチェック func (mac MACAddress) IsMulticast() bool { return (mac.bytes[0] & 0x01) == 1 && !mac.IsBroadcast() } // BroadcastMAC はブロードキャストMACアドレスを返す func BroadcastMAC() MACAddress { return MACAddress{bytes: [6]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}} } 3.3 イーサネットフレームの実装 MACアドレスを含むイーサネットフレーム構造を実装します。 ...

January 11, 2026 · 12 min

Go言語でネットワークプログラミングを学ぶ - 第0章

参考・インスピレーション元 この教材は以下のサイトの構成を参考に、Go言語での実装として新たに構築したものです: CoNeCo|コンピュータネットワーク with Colab: https://www.conecolab.com/ 作者:中山悠(東京農工大学准教授) ライセンス:CC-BY-SA Google Colabを使用したPython実装によるネットワーク学習教材 本教材は上記の教育アプローチにインスピレーションを受けつつ、Go言語での独自実装として作成しています。 Go言語でネットワークプログラミングを学ぶ 第0章:環境構築とネットワーク基礎概念 0.1 環境構築 # Go 1.21以上をインストール go version # プロジェクト初期化 mkdir go-network-programming cd go-network-programming go mod init go-network-programming # 必要なパッケージ go get github.com/google/uuid go get gonum.org/v1/gonum/graph 0.2 なぜGo言語なのか? ネットワークプログラミングにおけるGo言語の利点: 並行処理のサポート:goroutineによる軽量な並行処理 型安全性:プロトコルの違いをコンパイル時に検証 シンプルな文法:複雑な仕様を直感的なコードで表現 標準ライブラリ:充実したネットワーク関連パッケージ 0.3 学習対象の基本概念 ノード (Node) ネットワーク上のデバイス(PC、スマートフォン、ルーターなど) パケットを送受信する機能 一意のアドレスを持つ リンク (Link) ノード間の接続 帯域幅、遅延、エラー率などの特性を持つ 双方向または単方向の通信 パケット (Packet) ネットワークで転送される情報の単位 ヘッダとペイロードから構成 プロトコル層によって内容が変化 0.4 基本アーキテクチャの設計 // ネットワークエンティティの基本インターフェース type NetworkEntity interface { ID() string String() string } // パケット処理のインターフェース type PacketHandler interface { Send(packet Packet, destination string) error Receive() <-chan Packet } // アドレス管理のインターフェース type Addressable interface { Address() Address SetAddress(addr Address) } 0.5 学習計画 第1章: 基本要素の実装 (Node, Link, Packet) 第2章: 時間と並行性の導入 第3章: スイッチングとMACアドレス 第4章: MACアドレス学習とループ回避 第5章: IPパケットとルーティング 第6章: 動的ルーティングプロトコル 第7章: レイヤ化とカプセル化 第8章: アドレス解決プロトコル 第9章: 動的IPアドレス設定とNAT 第10章: TCP接続の確立 第11章: 確認応答と再送制御 第12章: 輻輳制御とウィンドウ制御 第13章: QoSと優先制御 第14章: アプリケーション層プロトコル 第15章: セキュリティと暗号化 0.6 評価ポイント 各章で以下の観点から実装を評価します: ...

January 10, 2026 · 1 min

Go言語でネットワークプログラミングを学ぶ - 第1章

第1章:ネットワークの基本要素 - Node、Link、Packet 1.1 プロジェクト構造 go-network-programming/ ├── go.mod ├── go.sum ├── main.go ├── packet.go ├── node.go └── link.go この章では、ネットワークの基本的な構成要素であるノード、リンク、パケットをGo言語で実装します。実際のネットワーク機器と同じように、複数のプロセスが並行して動作し、channelを通じてパケットを送受信する仕組みを構築します。 1.2 パケットの実装 パケットは、ネットワークで送信される情報の基本単位です。送信元、宛先、データ本体、タイムスタンプなどの情報を含みます。 ファイル名: ./packet.go package main import ( "fmt" "time" "github.com/google/uuid" ) // Packet はネットワークで送信される基本単位を表現する // 実際のTCP/IPパケットのように、ヘッダ情報とペイロードを持つ type Packet struct { ID string // パケットの一意識別子 Source string // 送信元ノードの名前 Destination string // 宛先ノードの名前 Data []byte // 実際のデータ(ペイロード) Size int // データサイズ(バイト) Timestamp time.Time // パケット生成時刻 } // NewPacket は新しいパケットを生成する // 実際のネットワークスタックでパケットが生成される処理を模倣 func NewPacket(source, destination string, data []byte) *Packet { return &Packet{ ID: uuid.New().String(), Source: source, Destination: destination, Data: data, Size: len(data), Timestamp: time.Now(), } } // String はパケットの文字列表現を返す(デバッグ用) func (p *Packet) String() string { return fmt.Sprintf("Packet{ID: %s, From: %s, To: %s, Size: %d bytes}", p.ID[:8], p.Source, p.Destination, p.Size) } 1.3 ノードの実装 ノードは、ネットワーク上のデバイス(PC、スマートフォン、ルーターなど)を表現します。パケットの送受信機能を持ち、複数のリンクに接続できます。 ...

January 10, 2026 · 6 min

DFS/BFSの本質:深さと幅を支配するデータ構造の選択

DFS, BFSがわかりづらかったので、いくつかの記事を見て個人的に感じた疑問や 「こういうコード例が欲しい」という要望を踏まえて生成AIに生成してもらった。 生成された内容を検証し、コードを実際に動かして確認したところ、 自分の理解が深まる良い記事になったので、このまま公開することにした。 はじめに LeetCodeでMedium問題を解いていると、必ず遭遇するのがDFS(深さ優先探索)とBFS(幅優先探索)だ。 「Dは深さ、Bは幅」というのは知っている。でも、なぜスタックとキューを使い分けるのか?その本質を理解している人は意外と少ない。 今回は、入れ子リストの例を使って、DFS/BFSの動作原理とデータ構造の関係を視覚的に解説する。 問題設定:入れ子リストをフラット化する 以下のような入れ子構造のリストがあるとする。 data = [1, [4, 5, [6, 7, 8], 2], 3] これをフラットな配列にしたい。このとき、「どの順番で要素を取り出すか」がDFS/BFSの違いだ。 ツリー構造として可視化する 入れ子リストは、実はツリー構造として表現できる。 root / | \ 1 [] 3 | /|\ \ 4 5 [] 2 | /|\ 6 7 8 この木をどう巡回するかで、DFSとBFSが決まる。 DFS(深さ優先探索):とにかく深く潜る 動作イメージ 「見つけた枝があれば、まずそこを最後まで探索する」 訪問順序: 1 → [中に入る] → 4 → 5 → [さらに中] → 6 → 7 → 8 → [戻る] → 2 → [戻る] → 3 結果:[1, 4, 5, 6, 7, 8, 2, 3] ...

January 9, 2026 · 3 min