GAS×ChatGPTでスプレッドシートのデータ分析を自動化|レポート生成も
毎週月曜の朝、スプレッドシートを開いて売上データを集計し、前週と比較して、レポートにまとめる。この作業に30分以上かけているなら、GAS×ChatGPTで自動化する価値がある。
スプレッドシートのデータをGASで集計し、ChatGPTに分析させれば、傾向の把握からレポート生成、異常値検知まで自動で回る。データ分析の専門知識は不要で、AIが傾向を読み取って日本語で解説してくれる。コピペで使えるコード付きで、順を追って構築していこう。
この記事で作れるもの
機能1: 売上データの自動分析
スプレッドシートの売上データをChatGPTが分析し、傾向やインサイトを抽出する。
出力例:
【分析結果】
・今月の売上は前月比12%増加
・火曜日の売上が最も高い傾向
・商品Aの売上が急上昇(前月比35%増)
・東京エリアの成長率が顕著
【推奨アクション】
・火曜日に広告予算を増額
・商品Aの在庫確保を優先
機能2: 週次・月次レポートの自動生成
定期的にデータを集計し、読みやすいレポートを自動生成する。メールやSlackでの配信にも対応可能。
機能3: 異常値の自動検知
売上の急落、在庫切れリスク、コスト超過を自動検知し、アラートを送信する。
必要な準備
1. Googleアカウント
Googleスプレッドシートを使用する。
2. OpenAI APIキー
GAS×ChatGPT連携の基礎で取得方法を確認してほしい。
3. 分析対象のデータ
ここでは「売上データ」を例に進めるが、どんなデータにも応用できる。
STEP 1: サンプルデータを準備
1-1: スプレッドシートを作成
新しいGoogleスプレッドシートを作成し、「売上分析」と名前を付ける。
1-2: サンプルデータの構造
以下のような売上データを想定している。
| A列 | B列 | C列 | D列 | E列 |
|---|---|---|---|---|
| 日付 | 商品名 | カテゴリ | 売上金額 | エリア |
| 2026/01/01 | 商品A | 家電 | 15000 | 東京 |
| 2026/01/01 | 商品B | 雑貨 | 3500 | 大阪 |
| … | … | … | … | … |
1-3: シート構成
| シート名 | 用途 |
|---|---|
| 売上データ | 生データを格納 |
| 分析結果 | ChatGPTの分析結果を記録 |
| 設定 | パラメータ設定用 |
STEP 2: 基本コードを設定
2-1: スクリプトエディタを開く
スプレッドシートのメニューから「拡張機能」→「Apps Script」を選択する。
2-2: メインコードを入力
/**
* スプレッドシートデータ分析システム
* GAS × ChatGPT API
*/
// ========================================
// 設定
// ========================================
const CONFIG = {
DATA_SHEET: '売上データ',
RESULT_SHEET: '分析結果',
MODEL: 'gpt-4o-mini',
MAX_TOKENS: 2000,
TEMPERATURE: 0.5
};
// ========================================
// メイン: データ分析を実行
// ========================================
function runDataAnalysis() {
try {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const dataSheet = ss.getSheetByName(CONFIG.DATA_SHEET);
const resultSheet = ss.getSheetByName(CONFIG.RESULT_SHEET);
// データを取得
const data = getSheetData(dataSheet);
// 基本統計を計算
const stats = calculateBasicStats(data);
// ChatGPTで分析
const analysis = analyzeWithChatGPT(data, stats);
// 結果を記録
recordAnalysisResult(resultSheet, analysis, stats);
// メールで通知(任意)
sendAnalysisReport(analysis, stats);
console.log('分析完了');
} catch (error) {
console.error('分析エラー:', error);
notifyError(error);
}
}
// ========================================
// シートからデータを取得
// ========================================
function getSheetData(sheet) {
const lastRow = sheet.getLastRow();
const lastCol = sheet.getLastColumn();
if (lastRow < 2) {
throw new Error('データがありません');
}
// ヘッダーとデータを取得
const headers = sheet.getRange(1, 1, 1, lastCol).getValues()[0];
const values = sheet.getRange(2, 1, lastRow - 1, lastCol).getValues();
// オブジェクト配列に変換
return values.map(row => {
const obj = {};
headers.forEach((header, i) => {
obj[header] = row[i];
});
return obj;
});
}
// ========================================
// 基本統計を計算
// ========================================
function calculateBasicStats(data) {
// 売上金額の列名を想定
const salesColumn = '売上金額';
const sales = data
.map(row => Number(row[salesColumn]) || 0)
.filter(v => v > 0);
const total = sales.reduce((a, b) => a + b, 0);
const count = sales.length;
const average = count > 0 ? total / count : 0;
const max = Math.max(...sales);
const min = Math.min(...sales);
// カテゴリ別集計
const categoryTotals = {};
data.forEach(row => {
const cat = row['カテゴリ'] || '不明';
const amount = Number(row[salesColumn]) || 0;
categoryTotals[cat] = (categoryTotals[cat] || 0) + amount;
});
// エリア別集計
const areaTotals = {};
data.forEach(row => {
const area = row['エリア'] || '不明';
const amount = Number(row[salesColumn]) || 0;
areaTotals[area] = (areaTotals[area] || 0) + amount;
});
// 日別集計
const dailyTotals = {};
data.forEach(row => {
const date = row['日付'];
const dateStr = date instanceof Date
? Utilities.formatDate(date, 'JST', 'yyyy/MM/dd')
: String(date);
const amount = Number(row[salesColumn]) || 0;
dailyTotals[dateStr] = (dailyTotals[dateStr] || 0) + amount;
});
return {
total,
count,
average: Math.round(average),
max,
min,
categoryTotals,
areaTotals,
dailyTotals,
period: {
start: Object.keys(dailyTotals).sort()[0],
end: Object.keys(dailyTotals).sort().pop()
}
};
}
// ========================================
// ChatGPTで詳細分析
// ========================================
function analyzeWithChatGPT(data, stats) {
const apiKey = PropertiesService.getScriptProperties()
.getProperty('OPENAI_API_KEY');
if (!apiKey) {
throw new Error('APIキーが設定されていません');
}
// 統計情報をテキスト化
const statsText = formatStatsForPrompt(stats);
// サンプルデータ(最新20件)
const sampleData = data.slice(-20).map(row =>
`${row['日付']} | ${row['商品名']} | ${row['カテゴリ']} | ¥${row['売上金額']} | ${row['エリア']}`
).join('\n');
const prompt = `以下の売上データを分析し、ビジネスに役立つインサイトを提供してください。
【集計期間】
${stats.period.start} 〜 ${stats.period.end}
【基本統計】
${statsText}
【直近のデータサンプル(20件)】
${sampleData}
以下の形式でJSON出力してください。JSONのみ出力し、他の文章は含めないでください。
{
"summary": "全体の傾向を3文で要約",
"insights": [
"インサイト1(データから読み取れる重要な発見)",
"インサイト2",
"インサイト3"
],
"trends": {
"positive": ["良い傾向1", "良い傾向2"],
"negative": ["懸念点1", "懸念点2"]
},
"recommendations": [
"推奨アクション1",
"推奨アクション2",
"推奨アクション3"
],
"anomalies": ["異常値や注意すべき点があれば記載"]
}`;
const response = callChatGPT(apiKey, prompt);
try {
return JSON.parse(response);
} catch (e) {
// JSONパース失敗時
return {
summary: response.substring(0, 200),
insights: ['分析結果の解析に失敗しました'],
trends: { positive: [], negative: [] },
recommendations: ['手動で確認してください'],
anomalies: []
};
}
}
// ========================================
// 統計情報をプロンプト用にフォーマット
// ========================================
function formatStatsForPrompt(stats) {
let text = '';
text += `・総売上: ¥${stats.total.toLocaleString()}\n`;
text += `・取引件数: ${stats.count}件\n`;
text += `・平均単価: ¥${stats.average.toLocaleString()}\n`;
text += `・最高額: ¥${stats.max.toLocaleString()}\n`;
text += `・最低額: ¥${stats.min.toLocaleString()}\n\n`;
text += '【カテゴリ別売上】\n';
Object.entries(stats.categoryTotals)
.sort((a, b) => b[1] - a[1])
.forEach(([cat, amount]) => {
text += `・${cat}: ¥${amount.toLocaleString()}\n`;
});
text += '\n【エリア別売上】\n';
Object.entries(stats.areaTotals)
.sort((a, b) => b[1] - a[1])
.forEach(([area, amount]) => {
text += `・${area}: ¥${amount.toLocaleString()}\n`;
});
return text;
}
// ========================================
// ChatGPT API呼び出し
// ========================================
function callChatGPT(apiKey, prompt) {
const url = 'https://api.openai.com/v1/chat/completions';
const payload = {
model: CONFIG.MODEL,
messages: [
{
role: 'system',
content: 'あなたはビジネスデータアナリストです。売上データを分析し、実用的なインサイトと推奨アクションを提供してください。'
},
{
role: 'user',
content: prompt
}
],
max_tokens: CONFIG.MAX_TOKENS,
temperature: CONFIG.TEMPERATURE
};
const options = {
method: 'post',
contentType: 'application/json',
headers: {
'Authorization': 'Bearer ' + apiKey
},
payload: JSON.stringify(payload),
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const responseCode = response.getResponseCode();
if (responseCode !== 200) {
throw new Error(`API Error: ${responseCode}`);
}
const json = JSON.parse(response.getContentText());
return json.choices[0].message.content.trim();
}
// ========================================
// 分析結果を記録
// ========================================
function recordAnalysisResult(sheet, analysis, stats) {
const timestamp = new Date();
// 既存データをクリア(ヘッダー以外)
if (sheet.getLastRow() > 1) {
sheet.getRange(2, 1, sheet.getLastRow() - 1, 10).clear();
}
// 結果を書き込み
const row = 2;
sheet.getRange(row, 1).setValue(timestamp);
sheet.getRange(row, 2).setValue(stats.period.start + ' 〜 ' + stats.period.end);
sheet.getRange(row, 3).setValue(stats.total);
sheet.getRange(row, 4).setValue(stats.count);
sheet.getRange(row, 5).setValue(analysis.summary);
sheet.getRange(row, 6).setValue(analysis.insights.join('\n'));
sheet.getRange(row, 7).setValue(
'【良い傾向】\n' + analysis.trends.positive.join('\n') +
'\n\n【懸念点】\n' + analysis.trends.negative.join('\n')
);
sheet.getRange(row, 8).setValue(analysis.recommendations.join('\n'));
sheet.getRange(row, 9).setValue(analysis.anomalies.join('\n') || 'なし');
}
// ========================================
// メールでレポート送信
// ========================================
function sendAnalysisReport(analysis, stats) {
const email = Session.getActiveUser().getEmail();
const subject = `【売上分析レポート】${stats.period.start}〜${stats.period.end}`;
const body = `売上分析レポート
================
【集計期間】${stats.period.start} 〜 ${stats.period.end}
【基本統計】
・総売上: ¥${stats.total.toLocaleString()}
・取引件数: ${stats.count}件
・平均単価: ¥${stats.average.toLocaleString()}
【サマリー】
${analysis.summary}
【主なインサイト】
${analysis.insights.map((i, idx) => `${idx + 1}. ${i}`).join('\n')}
【傾向】
◎ 良い傾向
${analysis.trends.positive.map(t => `・${t}`).join('\n')}
△ 懸念点
${analysis.trends.negative.map(t => `・${t}`).join('\n')}
【推奨アクション】
${analysis.recommendations.map((r, idx) => `${idx + 1}. ${r}`).join('\n')}
【異常値・注意点】
${analysis.anomalies.length > 0 ? analysis.anomalies.join('\n') : 'なし'}
---
このレポートはGAS×ChatGPTで自動生成されました。`;
GmailApp.sendEmail(email, subject, body);
console.log('レポートメール送信完了');
}
// ========================================
// エラー通知
// ========================================
function notifyError(error) {
const email = Session.getActiveUser().getEmail();
GmailApp.sendEmail(
email,
'【エラー】売上分析システム',
`エラーが発生しました:\n\n${error.toString()}`
);
}
// ========================================
// 異常値検知(単独実行可能)
// ========================================
function detectAnomalies() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const dataSheet = ss.getSheetByName(CONFIG.DATA_SHEET);
const data = getSheetData(dataSheet);
const stats = calculateBasicStats(data);
// 標準偏差を計算
const sales = data.map(row => Number(row['売上金額']) || 0);
const mean = stats.average;
const variance = sales.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / sales.length;
const stdDev = Math.sqrt(variance);
// 異常値(平均から2標準偏差以上離れた値)を検出
const threshold = mean + 2 * stdDev;
const anomalies = data.filter(row => {
const amount = Number(row['売上金額']) || 0;
return amount > threshold || amount < mean - 2 * stdDev;
});
if (anomalies.length > 0) {
const message = `異常値を${anomalies.length}件検出しました:\n\n` +
anomalies.slice(0, 10).map(row =>
`${row['日付']} | ${row['商品名']} | ¥${row['売上金額']}`
).join('\n');
GmailApp.sendEmail(
Session.getActiveUser().getEmail(),
'【アラート】売上データに異常値を検出',
message
);
console.log(`異常値検出: ${anomalies.length}件`);
} else {
console.log('異常値なし');
}
}
// ========================================
// 前週比較分析
// ========================================
function compareWithLastWeek() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const dataSheet = ss.getSheetByName(CONFIG.DATA_SHEET);
const data = getSheetData(dataSheet);
const today = new Date();
const oneWeekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
const twoWeeksAgo = new Date(today.getTime() - 14 * 24 * 60 * 60 * 1000);
// 今週と先週のデータを分離
const thisWeek = data.filter(row => {
const date = new Date(row['日付']);
return date >= oneWeekAgo && date <= today;
});
const lastWeek = data.filter(row => {
const date = new Date(row['日付']);
return date >= twoWeeksAgo && date < oneWeekAgo;
});
const thisWeekTotal = thisWeek.reduce((sum, row) =>
sum + (Number(row['売上金額']) || 0), 0);
const lastWeekTotal = lastWeek.reduce((sum, row) =>
sum + (Number(row['売上金額']) || 0), 0);
const changeRate = lastWeekTotal > 0
? ((thisWeekTotal - lastWeekTotal) / lastWeekTotal * 100).toFixed(1)
: 'N/A';
const comparison = {
thisWeek: thisWeekTotal,
lastWeek: lastWeekTotal,
changeRate: changeRate,
trend: thisWeekTotal > lastWeekTotal ? '増加' : '減少'
};
console.log('週次比較:', comparison);
return comparison;
}
// ========================================
// テスト用: サンプルデータ生成
// ========================================
function generateSampleData() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(CONFIG.DATA_SHEET);
if (!sheet) {
sheet = ss.insertSheet(CONFIG.DATA_SHEET);
}
// ヘッダー
sheet.getRange(1, 1, 1, 5).setValues([
['日付', '商品名', 'カテゴリ', '売上金額', 'エリア']
]);
// サンプルデータ生成
const products = ['商品A', '商品B', '商品C', '商品D', '商品E'];
const categories = ['家電', '雑貨', '食品', '衣類'];
const areas = ['東京', '大阪', '名古屋', '福岡', '札幌'];
const data = [];
const startDate = new Date('2026-01-01');
for (let i = 0; i < 100; i++) {
const date = new Date(startDate.getTime() + i * 24 * 60 * 60 * 1000);
const product = products[Math.floor(Math.random() * products.length)];
const category = categories[Math.floor(Math.random() * categories.length)];
const amount = Math.floor(Math.random() * 50000) + 1000;
const area = areas[Math.floor(Math.random() * areas.length)];
data.push([date, product, category, amount, area]);
}
sheet.getRange(2, 1, data.length, 5).setValues(data);
console.log('サンプルデータを生成しました');
}
// ========================================
// 分析結果シートの初期化
// ========================================
function initResultSheet() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(CONFIG.RESULT_SHEET);
if (!sheet) {
sheet = ss.insertSheet(CONFIG.RESULT_SHEET);
}
// ヘッダー
sheet.getRange(1, 1, 1, 9).setValues([[
'分析日時', '対象期間', '総売上', '件数',
'サマリー', 'インサイト', '傾向', '推奨アクション', '異常値'
]]);
// 列幅調整
sheet.setColumnWidth(5, 300);
sheet.setColumnWidth(6, 300);
sheet.setColumnWidth(7, 300);
sheet.setColumnWidth(8, 300);
console.log('分析結果シートを初期化しました');
}
2-3: APIキーを設定
- 「プロジェクトの設定」→「スクリプトプロパティ」
OPENAI_API_KEYにAPIキーを設定
STEP 3: 初期設定とテスト
3-1: シートを初期化
- 関数選択で
initResultSheetを実行(分析結果シート作成) - 関数選択で
generateSampleDataを実行(テストデータ生成)
3-2: 分析を実行
- 関数選択で
runDataAnalysisを実行 - 「分析結果」シートに結果が記録される
- メールでレポートが届く
STEP 4: 定期実行の設定
4-1: 週次レポートの自動化
毎週月曜日の朝9時に分析を実行する設定。
- 「トリガー」→「トリガーを追加」
- 以下の設定で保存
| 項目 | 設定値 |
|---|---|
| 実行する関数 | runDataAnalysis |
| イベントのソース | 時間主導型 |
| 時間ベースのトリガータイプ | 週ベースのタイマー |
| 曜日 | 毎週月曜日 |
| 時刻 | 午前9時〜10時 |
4-2: 異常値検知の日次実行
毎日夕方に異常値をチェックする設定。
| 項目 | 設定値 |
|---|---|
| 実行する関数 | detectAnomalies |
| イベントのソース | 時間主導型 |
| 時間ベースのトリガータイプ | 日付ベースのタイマー |
| 時刻 | 午後6時〜7時 |
応用カスタマイズ
カスタマイズ1: 分析項目の追加
顧客データや在庫データなど、別のデータにも対応できる。
// 顧客分析用のプロンプト例
const customerPrompt = `以下の顧客データを分析し、以下を出力してください:
- 顧客セグメント(VIP/一般/休眠)
- 購買頻度の傾向
- 離脱リスクの高い顧客`;
カスタマイズ2: Slack連携
function sendToSlack(analysis) {
const webhookUrl = PropertiesService.getScriptProperties()
.getProperty('SLACK_WEBHOOK_URL');
const payload = {
blocks: [
{
type: 'header',
text: { type: 'plain_text', text: '📊 売上分析レポート' }
},
{
type: 'section',
text: { type: 'mrkdwn', text: `*サマリー*\n${analysis.summary}` }
},
{
type: 'section',
text: { type: 'mrkdwn', text: `*推奨アクション*\n${analysis.recommendations.join('\n')}` }
}
]
};
UrlFetchApp.fetch(webhookUrl, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
});
}
カスタマイズ3: グラフ自動生成
function createChart(sheet, stats) {
const chartBuilder = sheet.newChart()
.setChartType(Charts.ChartType.PIE)
.addRange(sheet.getRange('A2:B10'))
.setPosition(5, 7, 0, 0)
.setOption('title', 'カテゴリ別売上');
sheet.insertChart(chartBuilder.build());
}
トラブルシューティング
Q: データが多すぎてAPIに送れない
対策: サンプルデータを絞るか、統計情報のみを送信するようプロンプトを調整する。
// 直近100件に絞る
const sampleData = data.slice(-100);
Q: 分析結果が的外れ
対策: プロンプトを具体的に書き直す。業種や分析目的を明記すると精度が上がる。
const prompt = `あなたはEC事業のデータアナリストです。
以下は月間売上データです。特に以下の観点で分析してください:
1. 売れ筋商品の傾向
2. 顧客単価の変化
3. 季節性の影響`;
Q: JSONパースエラー
対策: プロンプトに「JSONのみ出力」を強調する。フォールバック処理も実装済みなので、仮にパースに失敗しても処理は止まらない。
まとめ
GAS×ChatGPTでスプレッドシートのデータ分析を自動化する方法を見てきた。
実現できること:
- 売上データの自動分析(傾向・インサイト抽出)
- 週次・月次レポートの自動生成
- 異常値の自動検知とアラート
- 前週比較分析
応用範囲
このシステムは売上データに限らず、以下にも応用が効く。
- 顧客アンケートの分析
- 在庫データの最適化提案
- 採用データの傾向分析
- SNSエンゲージメントの分析
関連記事
- GAS×ChatGPTでフォーム回答を自動処理 – アンケート分類の自動化
- GAS×ChatGPT連携の基礎 – 初めての方はこちら
- 自動メール返信Botの作り方 – メール自動化
- GAS×ChatGPT実践ユースケース5選 – 他の活用アイデア
- GASでスプレッドシートを自動化 – GASの基礎
コメント