店舗の数だけファイルを分割するpython拡張ノードをBobに作らせる
1.想定される利用目的
・店舗と特定の顧客/車載器/スマートフォンの位置を表示させる
2.サンプルストリームとデータのダウンロード
ストリーム
3.サンプルストリームの説明
a.入力するデータは以下の通りです。1000種類の地点のダミーデータです。
b.[データ型]ノードを編集します。
c.[拡張のエクスポート]ノードを編集します。スクリプトをどのようにBobに書かせたかは後述します。
出力するパスを変更して利用します。
[実行]します。任意の場所にhtmlファイルが生成されますので展開します。
Bobによるスクリプトの作成手順
1.Bobを起動して、作業フォルダを指定します。
2.SPSS Modeler[拡張のエクスポート]ノードからサンプル構文をコピーします。
3.プロンプトを作成します。サンプル構文をペーストします。
プロンプト
Data.xlsxを読み込み
SPSS Modelerの拡張のエクスポートノードで座標の地点をOpenStreetMapに表示するするネイティブPython APIスクリプトを書いてください。
結果はHTMLファイルにして
/Users/aa517821/Desktop
に保存してください。
- 拡張のエクスポートノードのスクリプトの解説として
@https://www.ibm.com/docs/ja/spss-modeler/18.6.0?topic=spark-native-python-apis を参考にしてください。
- シバンは不要です。
###Use this code to import data from Modeler to Python
#import modelerpy
#modelerData = modelerpy.readPandasDataframe()
#modelerDataModel = modelerpy.getDataModel()
4.プロンプトを実行します。
5.Bobの以下の出力を[拡張のエクスポート]にそのまま利用します。
Pythonスクリプト
# SPSS Modeler 拡張エクスポートノード用スクリプト
# OpenStreetMapで座標データを表示するHTMLファイルを生成
import pandas as pd
import json
from datetime import datetime
# Modelerからデータを読み込む
# 注: 実際のSPSS Modeler環境では以下のコメントを解除してください
# import modelerpy
# modelerData = modelerpy.readPandasDataframe()
# modelerDataModel = modelerpy.getDataModel()
# テスト用: data.xlsxから直接読み込み(実際の環境では上記のmodelerpy使用)
try:
modelerData = pd.read_excel('data.xlsx')
except:
# Modeler環境での実行を想定
import modelerpy
modelerData = modelerpy.readPandasDataframe()
modelerDataModel = modelerpy.getDataModel()
# データの検証
if 'latitude' not in modelerData.columns or 'longitude' not in modelerData.columns:
raise ValueError("データに'latitude'と'longitude'列が必要です")
# 海上・河川判定関数
def is_on_land(lat, lng):
"""陸地上のポイントかどうかを判定"""
# 東京湾の範囲
if lat < 35.45 and lng > 139.7 and lng < 140.0:
return False
if lat < 35.55 and lng > 139.75 and lng < 139.95:
return False
# 隅田川周辺
if lng > 139.78 and lng < 139.81 and lat > 35.63 and lat < 35.73:
return False
# 荒川周辺
if lng > 139.78 and lng < 139.85 and lat > 35.68 and lat < 35.78:
return False
# 多摩川河口付近
if lat < 35.55 and lng > 139.70 and lng < 139.75:
return False
return True
# 陸地上のデータのみフィルタリング
filtered_data = modelerData[modelerData.apply(
lambda row: is_on_land(row['latitude'], row['longitude']), axis=1
)]
# データをJSON形式に変換
map_data = filtered_data.to_dict('records')
map_data_json = json.dumps(map_data, ensure_ascii=False, indent=2)
# 虎ノ門の座標
toranomon_lat = 35.6676
toranomon_lng = 139.7502
# HTMLファイルの内容を生成
html_content = f'''<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SPSS Modeler - 座標データマップ</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css" />
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
}}
#map {{
height: 100vh;
width: 100%;
}}
#info {{
position: absolute;
top: 10px;
right: 10px;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
min-width: 250px;
}}
#info h3 {{
margin: 0 0 15px 0;
color: #333;
font-size: 18px;
}}
#info p {{
margin: 8px 0;
color: #666;
font-size: 14px;
}}
#info .count {{
font-size: 36px;
font-weight: bold;
color: #0066CC;
margin: 10px 0;
text-align: center;
}}
#info .label {{
font-size: 12px;
color: #999;
text-align: center;
margin-bottom: 15px;
}}
#info .timestamp {{
font-size: 11px;
color: #999;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #eee;
}}
#controls {{
position: absolute;
top: 10px;
left: 10px;
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
}}
#controls button {{
background: #0066CC;
color: white;
border: none;
padding: 12px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
margin: 5px 0;
width: 100%;
transition: background 0.3s;
font-weight: 500;
}}
#controls button:hover {{
background: #0052A3;
}}
#controls button:active {{
background: #003D7A;
transform: scale(0.98);
}}
.leaflet-popup-content {{
margin: 12px;
min-width: 200px;
}}
.leaflet-popup-content h4 {{
margin: 0 0 12px 0;
color: #333;
border-bottom: 2px solid #0066CC;
padding-bottom: 8px;
font-size: 16px;
}}
.leaflet-popup-content p {{
margin: 6px 0;
color: #555;
font-size: 14px;
}}
.leaflet-popup-content strong {{
color: #333;
font-weight: 600;
}}
.marker-cluster-small {{
background-color: rgba(0, 102, 204, 0.6);
}}
.marker-cluster-small div {{
background-color: rgba(0, 102, 204, 0.8);
}}
.marker-cluster-medium {{
background-color: rgba(0, 102, 204, 0.6);
}}
.marker-cluster-medium div {{
background-color: rgba(0, 102, 204, 0.8);
}}
.marker-cluster-large {{
background-color: rgba(0, 102, 204, 0.6);
}}
.marker-cluster-large div {{
background-color: rgba(0, 102, 204, 0.8);
}}
</style>
</head>
<body>
<div id="controls">
<button onclick="toggleClustering()">🔄 クラスタリング切替</button>
<button onclick="fitAllMarkers()">🗺️ 全体表示</button>
</div>
<div id="info">
<h3>📍 SPSS Modeler データ</h3>
<div class="count" id="count">{len(filtered_data)}</div>
<div class="label">地点</div>
<p style="font-size: 12px; line-height: 1.5;">
<small>✓ マーカークリックで詳細表示<br>
✓ ズームでクラスタ展開<br>
✓ ドラッグで地図移動<br>
✓ 海上・河川上を除外</small>
</p>
<div class="timestamp">
生成日時: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
</div>
</div>
<div id="map"></div>
<script>
// データを埋め込み
const mapData = {map_data_json};
// マップを初期化(東京中心)
const map = L.map('map').setView([35.6762, 139.6503], 11);
// CartoDB Positronタイルレイヤーを追加
L.tileLayer('https://{{s}}.basemaps.cartocdn.com/light_all/{{z}}/{{x}}/{{y}}{{r}}.png', {{
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>',
subdomains: 'abcd',
maxZoom: 20
}}).addTo(map);
// マーカークラスタグループを作成
let markerClusterGroup = L.markerClusterGroup({{
chunkedLoading: true,
chunkInterval: 200,
chunkDelay: 50,
maxClusterRadius: 50,
spiderfyOnMaxZoom: true,
showCoverageOnHover: false,
zoomToBoundsOnClick: true,
iconCreateFunction: function(cluster) {{
const count = cluster.getChildCount();
let size = 'small';
if (count > 100) size = 'large';
else if (count > 50) size = 'medium';
return L.divIcon({{
html: '<div><span>' + count + '</span></div>',
className: 'marker-cluster marker-cluster-' + size,
iconSize: L.point(40, 40)
}});
}}
}});
let markers = [];
let allBounds = null;
let clusteringEnabled = true;
// 虎ノ門の座標
const targetLat = {toranomon_lat};
const targetLng = {toranomon_lng};
// マーカーを作成
function createMarkers(data) {{
const markerArray = [];
data.forEach((item) => {{
const lat = parseFloat(item.latitude);
const lng = parseFloat(item.longitude);
if (isNaN(lat) || isNaN(lng)) {{
console.warn(`無効な座標: ID ${{item.ID}}`);
return;
}}
// 虎ノ門の座標に最も近いかチェック(距離0.001度以内)
const distance = Math.sqrt(Math.pow(lat - targetLat, 2) + Math.pow(lng - targetLng, 2));
const isTarget = distance < 0.001;
// 円形マーカーを作成
const marker = L.circleMarker([lat, lng], {{
radius: isTarget ? 10 : 6,
fillColor: isTarget ? '#0000FF' : '#FF0000',
color: '#FFFFFF',
weight: isTarget ? 3 : 2,
opacity: 1,
fillOpacity: isTarget ? 0.9 : 0.7
}});
// ポップアップを設定
marker.bindPopup(`
<div>
<h4>📍 データポイント #${{item.ID}}</h4>
<p><strong>ID:</strong> ${{item.ID}}</p>
<p><strong>緯度:</strong> ${{lat.toFixed(6)}}</p>
<p><strong>経度:</strong> ${{lng.toFixed(6)}}</p>
</div>
`);
markers.push(marker);
markerArray.push([lat, lng]);
}});
// マーカークラスタグループに追加
markerClusterGroup.addLayers(markers);
map.addLayer(markerClusterGroup);
// 件数を表示
document.getElementById('count').textContent = data.length.toLocaleString();
// すべてのマーカーの境界を計算
if (markerArray.length > 0) {{
allBounds = L.latLngBounds(markerArray);
map.fitBounds(allBounds, {{ padding: [50, 50] }});
}}
console.log(`✓ ${{data.length}}件のデータポイントを表示しました`);
}}
// クラスタリングの切り替え
function toggleClustering() {{
if (clusteringEnabled) {{
// クラスタリングを無効化
map.removeLayer(markerClusterGroup);
markers.forEach(marker => marker.addTo(map));
clusteringEnabled = false;
console.log('クラスタリング: OFF');
}} else {{
// クラスタリングを有効化
markers.forEach(marker => map.removeLayer(marker));
map.addLayer(markerClusterGroup);
clusteringEnabled = true;
console.log('クラスタリング: ON');
}}
}}
// 全体表示
function fitAllMarkers() {{
if (allBounds) {{
map.fitBounds(allBounds, {{ padding: [50, 50] }});
}}
}}
// データを読み込んで表示
if (mapData && mapData.length > 0) {{
createMarkers(mapData);
}} else {{
console.error('データが見つかりません');
alert('エラー: データが読み込まれていません');
}}
</script>
</body>
</html>'''
# HTMLファイルを保存
output_path = '/Users/aa517821/Desktop/spss_modeler_map_output.html'
with open(output_path, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"✓ HTMLファイルを生成しました: {output_path}")
print(f"✓ 表示データ数: {len(filtered_data)}件(元データ: {len(modelerData)}件)")
print(f"✓ 除外データ数: {len(modelerData) - len(filtered_data)}件(海上・河川上)")
# SPSS Modeler環境では、データモデルを設定
# 注: 実際のSPSS Modeler環境では以下のコメントを解除してください
# modelerpy.setOutputDataModel(modelerDataModel)
# Made with Bob
注意事項
別のプロンプトを偶然引き継ぎ、海上座標へのプロットを回避しているようですが
実際には有効になっていません。
4.参考情報
店舗の数だけファイルを分割するpython拡張ノードをBobに作らせる
Modelerスクリプトでループ処理を実行する
SPSS Modeler ノードリファレンス目次
SPSS Modeler 逆引きストリーム集(データ加工)









