2024年5月28日火曜日

スプレッドシートのデータ分析を AI(GPT)に手伝ってもらう GAS ライブラリを作成しました


スプレッドシート上のデータの分析を AI(GPT)に手伝ってもらう GAS ライブラリを作りました。Google Apps Script 上で直接分析、または分析の提案を受けることができます(どういった分析になるかはデータによります)。

GAS ライブラリは、欠損値・重複値の探索を行う GAS ライブラリ以来、久しぶりに作りました。ライブラリ化しておくことで、いつでもお手軽に使えます。

GPT を使用するため、OpenAI の API キーが必要となります。


スクリプト ID

1kwJbmRU1hKumO3I_HeO2leId8SiTojKsdZOx5DsYTUWrqkrenj9OEq3W


ライブラリの追加方法:

  1. ライブラリの追加から上記スクリプト ID を入力して「検索」を押下
  2. バージョンを選択、任意の ID を指定して「追加」を押下

※ バージョンは最新のもの、もしくは「HEAD(開発モード)」を指定してください。

「HEAD(開発モード)」であれば常に最新の状態が維持されますが、バージョンに反映前の変更も加わりますので、予期せぬ不具合が発生する可能性があります(まぁ、最新バージョンでもありますが)。


使い方:

スクリプトの実行方法は、以下の通りです。

ライブラリの追加の際に指定した任意の ID.aiAnalysis(スプレッドシート ID, シート名, データ範囲, OpenAI の API キー, [システムプロンプト, ユーザープロンプト, 使用するモデル(default: gpt-4o), 生成する文章の最大トークン数(default: 4096), 生成された文章のランダムさ(default: 0.5)]*任意)

戻り値として GPT による回答を返します。

リファレンスはこちら


例えば test という ID でライブラリで読み込んで使う場合、以下のようなコードになります。

function myFunction() {
  const spId = "スプレッドシート ID";
  const shName = "シート名";
  const dataRange = "A1:C3";
  const key = "OpenAI API キー"

  Logger.log(test.aiAnalysis(spId, shName, dataRange, key));

  // 任意の設定
  /**
  const system = ""; // システムプロンプト
  const user = ""; // ユーザープロンプト
  const model = ""; // デフォルトは gpt-4o
  const max_tokens = 1024; // デフォルトは 4096
  const temperature = 0; // デフォルトは 0.5

  const specified = [system, user, model, max_tokens, temperature]

  Logger.log(test.aiAnalysis(spId, shName, dataRange, key, specified));
   */
}


以下は、試しに2020-2021年の原木椎茸の栽培データを投げてみた結果です。

データを分析するために、以下のステップを踏みます:

### 1. データの概要を把握
まず、データの全体的な構造と各列の意味を把握します。データには以下の列があります:
- 発生ID
- 発生開始日
- 品種
- 使用回数
- 棚番号
- 収穫日
- 積算温度(℃時)
- 平均温度(℃)
- 分散(温度)
- 標準偏差(温度)
- 平均湿度(%)
- 分散(湿度)
- 標準偏差(湿度)
- タイミング
- 品質
- 収穫までの日数
- 参考値

### 2. データのクリーニング
データに欠損値や異常値がないか確認し、必要に応じて処理します。

### 3. 基本統計量の計算
各数値列の基本統計量(平均、標準偏差、最小値、最大値など)を計算します。

### 4. データの可視化
データの分布や相関関係を可視化するために、ヒストグラム、散布図、箱ひげ図などを作成します。

### 5. 品質に影響を与える要因の特定
品質(良、優、可、不可)に影響を与える要因を特定するために、各要因と品質の関係を分析します。

### 6. モデルの構築
品質を予測するためのモデルを構築します。例えば、ロジスティック回帰や決定木などを使用します。

### 7. 結果の解釈と提言
分析結果を解釈し、品質向上のための提言を行います。

それでは、ステップごとに詳細に進めていきます。

### 1. データの概要を把握
データの全体的な構造を確認します。

```python
import pandas as pd

# データの読み込み
data = pd.read_csv('data.csv')

# データの概要を表示
print(data.info())
print(data.head())
```

### 2. データのクリーニング
欠損値や異常値がないか確認します。

```python
# 欠損値の確認
print(data.isnull().sum())

# 異常値の確認(数値列の基本統計量を表示)
print(data.describe())
```

必要に応じて欠損値の補完や異常値の処理を行います。

### 3. 基本統計量の計算
各数値列の基本統計量を計算します。

```python
# 基本統計量の計算
print(data.describe())
```

### 4. データの可視化
データの分布や相関関係を可視化します。

```python
import matplotlib.pyplot as plt
import seaborn as sns

# ヒストグラムの作成
data.hist(bins=30, figsize=(20, 15))
plt.show()

# 散布図行列の作成
sns.pairplot(data)
plt.show()
```

### 5. 品質に影響を与える要因の特定
品質(良、優、可、不可)に影響を与える要因を特定します。

```python
# 品質と各要因の関係を分析
sns.boxplot(x='品質', y='平均温度(℃)', data=data)
plt.show()

sns.boxplot(x='品質', y='平均湿度(%)', data=data)
plt.show()

# 相関行列の作成
corr_matrix = data.corr()
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm')
plt.show()
```

### 6. モデルの構築
品質を予測するためのモデルを構築します。

```python
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

# 特徴量とターゲットの分割
X = data[['平均温度(℃)', '平均湿度(%)', '積算温度(℃時)', '収穫までの日数']]
y = data['品質']

# データの分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# ロジスティック回帰モデルの構築
model = LogisticRegression()
model.fit(X_train, y_train)

# 予測と評価
y_pred = model.predict(X_test)
print(accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred))
```

### 7. 結果の解釈と提言
分析結果を解釈し、品質向上のための提言を行います。

```markdown
# 結果の解釈

- 平均温度、平均湿度、積算温度、収穫までの日数が品質に影響を与えていることが分かりました。
- ロジスティック回帰モデルにより、品質の予測精度は約XX%でした。

# 提言

- 平均温度をXX℃に保つことで、品質が向上する可能性があります。
- 平均湿度をXX%に保つことで、品質が向上する可能性があります。
- 収穫までの日数をXX日に調整することで、品質が向上する可能性があります。
```

以上がデータ分析のステップです。具体的なデータを用いた分析の実行は、Pythonのコードを実際に実行する必要があります。


Python を用いた分析手順を解説してくれていますね。

デフォルトでは、ざっくりしたプロンプトで指示しているのですが、具体的な指示を与えれば、より詳細な分析もしてくれると思います。


本ライブラリに関する今後の更新は cultivationdata.net で行います。

2024年5月20日月曜日

GAS で Gemini API を使った LINE bot を作る

-----
・追求(2024-6-20)
cultivationdata.net で、GPT と Gemini を搭載した LINE bot の作り方を公開しました。
-----

先日、GPT-4o を使った LINE bot を作りましたが、今回は Google の生成 AI である Gemini の API を使った LINE bot を GAS(Google Apps Script)で作ってみました。

とりあえず、継続的なやりとりや temperature の指定、画像処理もできるようにしました。

LINE Messaging API チャンネルの作成とアクセストークンの取得。及び、Gemini の API キーの取得は済んでる想定です。

それぞれ、GAS の「プロジェクトの設定」よりスクリプト プロパティとして追加・保存しておきます。プロパティ名は「Line_key」と「Gemini_key」としています。

また、スプレッドシート(サンプル)を用意して、「chat」シートと「temperature」シートを作っておきます。(作り方は「GAS で ChatGPT とやりとりできる簡単な LINE bot の作り方」を参照)


★ 機能一覧
  • 文頭に「続き)」を付けることで会話を継続。(例:「続き)もっと詳細に教えて」)
  • 文頭に「画像)」で画像の処理。(例:「画像)何ですか?」 → 画像を送信)
  • 文末に「(厳密」「(創造」で temperature を指定。通常 0.5、厳密 0、創造 1。(例:「3行で小噺を作って(創造」)


コードは以下の通りです。

const spreadsheet = SpreadsheetApp.openById('スプレッドシート ID');

const chatSheet = spreadsheet.getSheetByName('chat');
let chatLastRow = chatSheet.getLastRow();
let chatRange = chatSheet.getRange(2, 1, chatLastRow, 2);

const tempSheet = spreadsheet.getSheetByName('temperature');
const tempRange = tempSheet.getRange(1, 1);

function doPost(e) {
  try {
    const event = JSON.parse(e.postData.contents).events[0];
    let prompt = [];
    let replyMessage = '';

    switch(event.message.type) {
      case 'text': // リクエストがテキストの場合
        let messageContent = event.message.text;

        if (messageContent.startsWith('画像)')) {
          // 画像処理の場合
          messageContent = messageContent.replace('画像)', '');
          geminiRequestText(messageContent); // リクエスト(テキスト)を成型・保存
          sendLineMessage(event.replyToken, '画像を送信してください'); 
        } else {
          // 画像処理以外の場合
          prompt = geminiRequestText(messageContent);
          replyMessage = getGeminiReply(prompt); // Gemini で回答
          setAiMessage('model', replyMessage); // AI の返答をスプレッドシートに'role': 'model'で格納
          sendLineMessage(event.replyToken, replyMessage);
        }
        break;
      case 'image': // リクエストが画像の場合
        prompt = geminiRequestImage(event.message.id);
        replyMessage = getGeminiReply(prompt); // Gemini で回答
        sendLineMessage(event.replyToken, replyMessage);
        break;
      default:
        // リクエストがサポート外のデータ形式の場合
        sendLineMessage(event.replyToken, 'サポート外のデータ形式です');
    }
  } catch {
    // エラー発生時
    sendLineMessage(event.replyToken, '不明なエラーが発生しました');
  }
}

// リクエストデータ(テキスト)を生成する関数
function geminiRequestText(messageContent) {
  // リクエスト内容の処理
  if (messageContent.startsWith('続き)')) {
    // 文頭に「続き)」で会話を継続
    messageContent = messageContent.replace('続き)', '');
    chatSheet.getRange(chatLastRow + 1, 1, 1, 2).setValues([['user', messageContent]]);
  } else {
    // temperature を取得してスプレッドシートにセット
    // 文末に「(創造」「(厳密」で指定
    if (messageContent.endsWith('(創造')) {
      messageContent = messageContent.replace('(創造', ''); // 1
      tempRange.setValue(1);
    } else if (messageContent.endsWith('(厳密')) {
      messageContent = messageContent.replace('(厳密', ''); // 0
      tempRange.setValue(0);
    } else {
      tempRange.setValue(0.5);
    }

    chatRange.clear(); // 以前のチャット内容を削除

    // リクエスト内容をスプレッドシートに格納
    chatSheet.getRange(2, 1, 1, 2).setValues([['user', messageContent]]);
    chatRange = chatSheet.getRange(2, 1, 1, 2);
  }

  // リクエストデータを生成
  let contents = [];
  let values = chatRange.getValues();
  for(let i = 0; i < values.length; i++) {
      contents.push({'role': values[i][0], 'parts': {'text': values[i][1]}});
  }

  return contents;
}

// Gemini でリクエストデータ(画像)を生成する関数
function geminiRequestImage(id) {
  // 画像を取得
  const response = UrlFetchApp.fetch('https://api-data.line.me/v2/bot/message/' + id + '/content',{
    'headers': {
      'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('Line_key')}`,
    },
    'method': 'get'
  });
  const imageBlob = response.getBlob().getAs('image/jpeg');

  // 画像を Base64 にエンコード
  const base64Image = Utilities.base64Encode(imageBlob.getBytes());

  // リクエストデータを生成
  let contents = [{"parts": [{"text": chatSheet.getRange(2, 2).getValue()}, {"inlineData": {"mimeType": "image/jpeg", "data": base64Image}}]}];

  return contents;
}

// Gemini から回答を得る関数
function getGeminiReply(prompt) {
  let url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${PropertiesService.getScriptProperties().getProperty('Gemini_key')}`;

  payload = {
    'contents': prompt,
    'generationConfig': {
      'maxOutputTokens': 1024, // 生成する文章の最大トークン数
      'temperature': tempRange.getValue() // 生成された文章のランダムさを制御するパラメータ。値が高いほど、よりランダムな文章が生成される
    }
  };

  const options = {
    'payload': JSON.stringify(payload),
    'method' : 'POST',
    'muteHttpExceptions': true,
    'contentType':'application/json'
  };

  try {
    const response = JSON.parse(UrlFetchApp.fetch(url, options).getContentText());
    return response.candidates[0].content.parts[0].text; // Gemini の回答
  } catch {
    // エラー発生時
    return 'エラー: Gemini の回答が得られませんでした';
  }
}

// AI の返答をスプレッドシートに格納する関数
function setAiMessage(role, replyMessage) {
  chatLastRow = chatSheet.getLastRow();
  chatSheet.getRange(chatLastRow + 1, 1, 1, 2).setValues([[role, replyMessage]]);
}

// LINE メッセージを送信する関数
function sendLineMessage(replyToken, replyMessage) {
  const linePayload = {
    'method': 'post',
    'headers': {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('Line_key')}`,
    },
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': replyMessage,
      }],
    }),
  };

  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', linePayload);
}
※ 「gemini-1.5-flash-latest」モデルを使ったコードに変更しました(2024-6-1)

1行目の「スプレッドシート ID」は書き換えてください。

コード作成後にウェブアプリとして公開、URL を LINE Messaging API チャンネルで Webhook として設定して完成です。


使い方は、GPT のものと同じです。(ただ、システム指示はできません)



***

今回 Gemini をはじめて触りました。GPT だけでなく、こちらもしっかり追っていければと思います。

LINE bot に関しては、GPT とGemini を切り替えて使えるようにしておくと便利そうな気がします。

2024年5月17日金曜日

GAS で GPT-4o API を使った画像処理ができる LINE bot を作る

-----
・追記(2024-5-21)
Gemini の LINE bot も作ってみました → 「GAS で Gemini API を使った LINE bot を作る

・追求(2024-6-20)
cultivationdata.net で、GPT 及び Gemini を搭載した LINE bot の作り方を公開しました。
-----

先日発表された GPT-4o が、いろいろすごいみたいですね。

すでに API の利用ができるそうなので、以前、GAS(Google Apps Script) で作った LINE bot(「GAS で ChatGPT とやりとりできる簡単な LINE bot の作り方」)に画像処理機能を追加してみました。

スプレッドシート(サンプル)の準備等、基本的な作り方は、以前作った LINE bot と同じです。


★ 機能一覧
  • 文頭に「続き)」を付けることで会話を継続。(例:「続き)もっと詳細に教えて」)
  • 文頭に「昔話!」のように付けることで、スプレッドシートの「system」シートにあらかじめ登録したシステム指示を実行。(例:「昔話!きのこ」)
  • 文頭に「画像)」で画像の処理モード。(例:「画像)何ですか?」 → 画像を送信)
  • 文末に「(厳密」「(創造」で temperature を指定。通常 0.5、厳密 0、創造 1。(例:「3行で小噺を作って(創造」)


GAS のコードを、以下のように書き換えます。

const spreadsheet = SpreadsheetApp.openById('スプレッドシート ID');

const chatSheet = spreadsheet.getSheetByName('chat');
let chatLastRow = chatSheet.getLastRow();
let chatRange = chatSheet.getRange(2, 1, chatLastRow, 2);

const systemSheet = spreadsheet.getSheetByName('system');
const systemLastRow = systemSheet.getLastRow();
const systemWatchwordRange = systemSheet.getRange(2, 1, systemLastRow - 1, 1);
const systemContentRange = systemSheet.getRange(2, 2, systemLastRow - 1, 1);

const tempSheet = spreadsheet.getSheetByName('temperature');
const tempRange = tempSheet.getRange(1, 1);

function doPost(e) {
  try {
    const event = JSON.parse(e.postData.contents).events[0];

    switch (event.message.type){
      case 'text': // リクエストがテキストの場合
        let messageContent = event.message.text;

        if (messageContent.startsWith('画像)')) {
          // 画像処理の場合
          messageContent = messageContent.replace('画像)', '');
          getRequestMessage(messageContent); // リクエスト(テキスト)を成型・保存
          sendLineMessage(event.replyToken, '画像を送信してください');  // LINE で応答
        } else {
          // 画像処理以外の場合
          const requestData = getRequestMessage(messageContent); // リクエスト(テキスト)を成型・保存
          const replyMessage = generateOpenAiReply(requestData); // GPT で回答
          setAssistantMessage(replyMessage); // スプレッドシートに'role': 'assistant'で格納
          sendLineMessage(event.replyToken, replyMessage); // LINE で応答
        }
        break;
      case 'image': // リクエストが画像の場合
        const requestData = getRequestImage(event.message.id); // リクエスト(画像)を成型・保存
        const replyMessage = generateOpenAiReply(requestData); // GPT で回答
        setAssistantMessage(replyMessage); // スプレッドシートに'role': 'assistant'で格納
        sendLineMessage(event.replyToken, replyMessage); // LINE で応答
        break;
      default:
        // リクエストがサポート外のデータ形式の場合
        sendLineMessage(event.replyToken, 'サポート外のデータ形式です'); // LINE で応答
    }
  } catch {
    // エラー発生時
    sendLineMessage(event.replyToken, 'エラーが発生しました'); // LINE で応答
  }
}

// リクエストデータ(テキスト)を生成する関数
function getRequestMessage(messageContent) {
  // system ロールの配列を生成
  const systemWatchwordArry = systemWatchwordRange.getValues().flat();
  const systemContentArry = systemContentRange.getValues().flat();
  let systemWatchword = '';
  let systemContent = '';

  // リクエスト内容の処理
  if (messageContent.startsWith('続き)')) {
    // 文頭に「続き)」で会話を継続
    messageContent = messageContent.replace('続き)', '');
    chatSheet.getRange(chatLastRow + 1, 1, 1, 2).setValues([['user', messageContent]]);
  } else {
    // temperatureを取得してスプレッドシートにセット
    // 文末に「(創造」「(厳密」で指定
    if (messageContent.endsWith('(創造')) {
      messageContent = messageContent.replace('(創造', ''); // 1
      tempRange.setValue(1);
    } else if (messageContent.endsWith('(厳密')) {
      messageContent = messageContent.replace('(厳密', ''); // 0
      tempRange.setValue(0);
    } else {
      tempRange.setValue(0.5);
    }
    // スプレッドシートにあらかじめ登録した system ロールの読み込み
    for (let i = 0; i < systemWatchwordArry.length; i++) {
      // 文頭に「〇〇!」でsystem ロールを指定
      if (messageContent.startsWith(systemWatchwordArry[i] + '!')) {
        systemWatchword = systemWatchwordArry[i] + '!';
        systemContent = systemContentArry[i];
        break;
      }
    }
    messageContent = messageContent.replace(systemWatchword, '');

    chatRange.clear(); // 以前のチャット内容を削除

    // リクエスト内容をスプレッドシートに格納
    if (systemContent) {
      chatSheet.getRange(2, 1, 2, 2).setValues([['system', systemContent], ['user', messageContent]]);
      chatRange = chatSheet.getRange(2, 1, 2, 2);
    } else {
      chatSheet.getRange(2, 1, 1, 2).setValues([['user', messageContent]]);
      chatRange = chatSheet.getRange(2, 1, 1, 2);
    }
  }

  // リクエストデータを生成
  let requestData = [];
  if (systemContent) {
    requestData.push({'role': 'system', 'content': systemContent});
  }
  let values = chatRange.getValues();
  for(let i = 0; i < values.length; i++) {
    requestData.push({'role': values[i][0], 'content': values[i][1]});
  }

  return requestData;
}

// リクエストデータ(画像)を生成する関数
function getRequestImage(id) {
  // 画像を取得
  const response = UrlFetchApp.fetch('https://api-data.line.me/v2/bot/message/' + id + '/content',{
    'headers': {
      'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('Line_key')}`,
    },
    'method': 'get'
  });
  const imageBlob = response.getBlob().getAs('image/png');

  // 画像を Base64 にエンコード
  const base64Image = Utilities.base64Encode(imageBlob.getBytes());

  // リクエストデータを生成
  let requestData = [];
  let values = chatSheet.getRange(2, 1, chatLastRow - 1, 2).getValues();
  for(let i = 0; i < values.length; i++) {
    if(values[i][0] == 'user') {
      requestData.push({'role': 'user', 'content': [{'type': 'text', 'text': values[i][1]},{'type': 'image_url', 'image_url': {'url': `data:image/png;base64,${base64Image}`}}]});
    } else {
      requestData.push({'role': values[i][0], 'content': values[i][1]});
    }
  }

  return requestData
}

// GPT から回答を得る関数
function generateOpenAiReply(requestMessage) {
  const openAiHeaders = {
    'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('OpenAI_key')}`,
    'Content-type': 'application/json',
    'X-Slack-No-Retry': 1
  };

  const openAiPayload = {
    'model': 'gpt-4o', // 使用するモデル
    'max_tokens': 1024, // 生成する文章の最大トークン数
    'temperature': tempRange.getValue(), // 生成された文章のランダムさを制御するパラメータ。値が高いほど、よりランダムな文章が生成される
    'messages': requestMessage // 生成する文章の元になるプロンプト
  };

  const openAiParams = {
    'method': 'POST',
    'headers': openAiHeaders,
    'payload': JSON.stringify(openAiPayload),
  };

  const response = UrlFetchApp.fetch('https://api.openai.com/v1/chat/completions', openAiParams);
  const resData = JSON.parse(response.getContentText());

  return resData.choices[0].message.content; // GPT の回答
}

// アシスタントの返答をスプレッドシートに格納する関数
function setAssistantMessage(replyMessage) {
  chatLastRow = chatSheet.getLastRow();
  chatSheet.getRange(chatLastRow + 1, 1, 1, 2).setValues([['assistant', replyMessage]]);
}

// LINE メッセージを送信する関数
function sendLineMessage(replyToken, replyMessage) {
  const linePayload = {
    'method': 'post',
    'headers': {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${PropertiesService.getScriptProperties().getProperty('Line_key')}`,
    },
    'payload': JSON.stringify({
      'replyToken': replyToken,
      'messages': [{
        'type': 'text',
        'text': replyMessage,
      }],
    }),
  };

  UrlFetchApp.fetch('https://api.line.me/v2/bot/message/reply', linePayload);
}
※ コードを一部修正しました。

画像処理に関連しない部分の大きな変更点ですが、GPT-4o はより人間っぽく振る舞うことができるようになったとのことなので、英語をかませるのをやめて、日本語で直接 API とやりとりするようにしました。


改めて、画像処理モードの使い方です。


文頭に「画像)」と付けてプロンプトを書くと画像処理のモードになります。

「画像を送信してください」と返答があるので、続けて画像を送ると処理が行われます。

画像処理モードでも、これまで通り、「画像)日本語!写真の料理のレシピを教えて(創造」のような形式で、system ロールへの指示や、temperature パラメータの指定もできます(詳細は以前の投稿を参照してください)。

ただ、「続き)」を使った継続的なやりとりはできません(あまり意味ないかな?と思って)。



面倒な書き起こしもらくらく(ちょっと間違ってるけど)。


***

ということで、GPT-4o を使って画像処理ができる LINE bot を作ってみました。

GAS で作れるとサーバーのことを考えなくていいので、とても楽です。

この簡便さと能力であれば、アイデア次第で、個人でも農業に役立つものがあれこれ作れそうですね。

2024年4月25日木曜日

【定期】家計の見直し(2024年4月)

立て続けになりますが、家計の見直しです。


経営と家計が一体化した家族経営では、家計の最適化が心強い生存基盤になります。翻って、家族や自身にとって本当に価値あるものが何なのか、確認する良い機会になります。


・mineo で光回線代替

昨年、ひかり電話で使っていた固定電話を解約したのですが、今回、とうとう光回線自体を解約しました。

うちの場合、安定的な回線速度を要求するような使い方もしないので、戸建ての高価な光回線の維持しておく必要があるものだろうかと常々考えていました。

そんなわけで、3年ごとの契約更新のタイミングですっぱり解約してみました。

(ちなみに、auひかりですが、電話一本で簡単に即日解約できました)

代替回線は、mineo のマイピタ20GBとパケット放題 Plusで、1,925円です。これで、ざっくり3,400円の固定費削減になりました。

新しい mineo 回線は SIM カードを Rakuten WiFi Pocket に入れて運用することにしました。Rakuten WiFi Pocket は、以前キャンペーン時に1円で入手したもので、使いどころがなくてしまってありました。

基本は、節約ON で1.5Mbps の速度にしていますが、それほど不便なく使えています。突発的な何がなければ3日間で10GB の制限にも引っかからなそうです。

もちろん、複数人で同時に使う際には若干の遅れを感じますが、コストパフォーマンスを考えれば待てる範囲です。また、いざという時は20GB 分の高速通信も可能です。


自宅の電波が良ければ様々な選択肢があり得るんですけどね。ドコモ回線ダメ、au 回も線アップロードが貧弱という状況で、ソフトバンク回線のみまともに使えます。mineo には長くサービスを続けてほしいです。


・今年度も「do!浜通り」でガソリンを入れる

今年度も「do!浜通り」によるポイント還元をやるようです。

1回目は、4月25日(木)〜5月8日(水)で、いわき市は、10%還元の2,500ポイント上限(決済方法あたり)です。

今回から、決済方法が増えて楽天ペイも使えるので、ありがたいですね。いつものごとくガソリンを入れます。


・年金改革

年金制度の改革が以下のように検討されているそうです。

① 厚生年金の対象拡大
② 基礎年金の納付期間 40年 → 45年
③ 基礎年金の給付抑制を早期停止
④ 在職老齢年金の見直し
⑤ 保険料の基準額の上限上げ

このままでは制度が保たない、というのはわかる気がします。先日、自身(30代後半)の基礎年金のトータル納付額を確認したところ、すでに両親の納付額の1.5倍になっていました。

結局のところ、個人としては、使える制度を使いながらうまくやっていくしかないですね。


・昨今の雑感

塵も積もれば何とやらで、固定費削減の効果は抜群ですね。

今年は施設の大きな修繕等を予定しています。小規模農家が生き残っていくためには、全方位にアンテナを張って、地道にコツコツやっていくしかありません。

頑張っていきましょう!


花海棠
ちょっと前の写真ですが「花海棠」

2024年3月29日金曜日

【定期】家計の見直し(2024年3月)

家計の見直しです。

経営と家計が一体化した家族経営では、家計の最適化が心強い生存基盤になります。翻って、家族や自身にとって本当に価値あるものが何なのか、確認する良い機会になります。

・povo と楽天モバイルのデュアル SIM に変更

最近は以下の通り運用していました。

本回線 povo:5分以内通話かけ放題(550円)と3GB/30日間(990円)
データ通信用 IIJmio:ギガプラン eSIM(ドコモ回線) 5GB(660円)と10GB(1,100円)を月ごとに行ったり来たり

合計 2,200 or 2,640円

そもそも自宅でドコモ回線が入らないためこんなややこしい運用になっているのですが、この povo と IIJmio のデュアル SIM ですとデータ通信回線や通信容量の頻繁な切り替えが必須でやや面倒でした。

また、au と ドコモの回線を保持しておくといざという時に安心かなと思っていたのですが、最近はドコモ回線の調子がいまいち(?)な感もあってあまり当てにならないかなと……。

そんなこんなで、データ通信用の回線を IIJmio → 楽天モバイルに変更しました。

本回線 povo:5分以内通話かけ放題(550円)
データ通信用 楽天モバイル:だいたい20GB以内(2,178円)

合計 2,728円


合計金額は上がっているのですが、番号通知はなくていいし音声の品質も問わない、かつ5分以上かかりそうな電話に関しては、楽天モバイルの Rakuten Link を使うことで無料になります。この分を考えると年間トータルでとんとん以下になりそうな気がします。(ただやはり、重要な電話にはやはり電波や音質の不安が拭えきれないので povo を利用)

また、この povo と楽天モバイルのデュアル SIM であれば基本的に回線の切り替えが必要ないので日常的な手間はありません。

それから、楽天モバイルは追加料金なく2GBの海外利用ができるので旅行の際には便利ですね。

楽天モバイルで足りない時には povo でもデータ容量の購入が可能なので安心です。ちなみに povo は、アプリにログイン済みであれば、外部のインターネット接続環境がなくても海外データトッピングをご購入できるそうです。楽天モバイルを使い切った後から購入できるので、地味にありがたい機能です。


・楽天でんきの動力プランがけっこうお安め?と思いきや

4月から電気料金が値下げになるとのことだったので、現在動力を契約している楽天でんきと変更先候補の東北電力の料金の比較をしてみました。

東北電力の動力(低圧電力)の場合、基本料金が1,300円89銭/kW、電力量料金が夏季27円09銭/kWh、冬季25円64銭です。加えて、燃料費等調整額と再生可能エネルギー発電促進賦課金がかかります。

対して楽天でんきの動力プランは、基本料金が695円/kW、電力量料金が26.60円/kWhとなっています。加えて、市場価格調整額と再生可能エネルギー発電促進賦課金がかかります。

うちの場合、1年のうち半年は利用していないので、基本料金が安い方がお得になります。(使用量が0kWhだと基本料金も半額になります)

これだけ見ると圧倒的に楽天でんきがお得に見えます。

しかし、24年2月の燃料費等調整額と市場価格調整額を確認したところ、燃料費等調整額が約-10円なのに対して市場価格調整額は0円になっています。

たぶん逆転しているタイミングもあると思うのですが、仮にこの差が1年間続くと、トータル金額的にはとんとんくらいになります。悩ましいところです……。

とりあえず、楽天でんきのままもう少し様子を見ようかと思います。


・新 NISA

今年始まった新しい NISA ですが、みなさまご活用なさってますでしょうか?

全く持って先の読めない昨今、経営の財務的な安定性を高めるとともに、個人としてもしっかりと生活基盤を整えておかねばなりません。

クレジットカードでの積立も10万円まで可能になりましたから、上手に使っていきたいところですね。

ちなみに農家の老後の備えとしては、国民年金(+付加年金)をしっかり納めつつ、農業者年金 or iDeCo をできる範囲でやり、あとは新 NISA に全力を出せば、ひとまず人事は尽くした感があります。これでダメだったら天命だと思って諦めようという心境です(笑)。

(もちろん状況によって、新 NISA よりも所得控除ができる農業者年金や iDeCo の枠を埋めるのが優先されるパターンもあると思います)


・昨今の雑感

小規模な農家にとって、これからますます厳しい時代になると言われています。

ことの因果はいろいろあれど、簡単に言えば「利幅が小さくなっているから、規模を大きくするしかないよね。大きくなれないならしょうがないよね」ということだと理解しています。

こうした中で、われわれのような小さな個人農家が生き残っていくためには、利幅が小さ過ぎて大きな経営体が参入しないところで独自の価値を創造するしかありません。そして、それこそ社会の豊かさの源なんじゃないかと思ったり、思わなかったり。

まぁ、言うは易しなんですが……。

もちろん、そもそも素直に時流に乗って規模拡大すればいいんじゃないのかという考えももっともです。ただ、大きい方は大きい方で、やはり淘汰が進むと思うのですよね。

であれば、やはり自分の得意な、もしくは好きな土俵で頑張るのがよさそうな気がします。


きな粉とキクラゲ

2024年3月2日土曜日

農業の6次化(加工品作り)は終わったのか。

農業の6次化、とりわけ加工品作りは一時期のブームを経て、だいぶ下火になった感があります。加工品作りに手を出すくらいなら栽培に力を入れた方がいいとの声もよく聞きます。

そんなわけで、なんやかんや丸9年くらいちょこちょこと加工品作りを作り続けてきて思うところを書いてみようと思います。


ブーム時によくあった加工品作りのパターンはだいたい以下のような感じでした。

  • 補助金を利用して商品開発をする
  • 原材料として余りものや規格外品を利用する
  • 地元の飲食店や加工業者とコラボする
  • 加工業者に外注して加工する
  • デザイナーにラベルやパッケージの制作を依頼してデパートで販売しているようなものを目指す
  • イベントで消費者に配ってアンケートを取る
  • コンテストに出す

上に列挙したパターンが個々に悪い訳ではありませんが、3つ、4つ揃ってくると、あやしくなってくる印象です。

では、なぜ当時、6次化をして加工品を作ることが流行ったのかと言うと、簡単にプラスアルファの利益が出せるというイメージがあったんじゃないかと想像しています。

利益が出るというイメージの根拠となったのは、今まで捨てていた余り物や規格外品を販売するのだからプラスになる、さらに初期費用を補助金でまかなえば盤石、という素朴なものだったと思います。

しかしながら、実際には、加工賃や容器、パッケージの費用が嵩み、ロットも少ないため高価格の商品が誕生。販路もないまま在庫を抱え、売れないし、売れても利益が出ない(そして、賞味期限が間近になり配る)……というような状況に陥った農家が少なくなかったんじゃないかと思います。

この頃、質が悪かったのが、ダメなパターンの加工品作りでも、コンサルタントやデザイナーのような方と絡み、自分で育てた農産物で加工品を作るというストーリーを作るとウケが良かったのですよね。しかし、ウケの良さと、売れるかどうかはまた別ですからね。

結局、栽培にせよ加工品作りにせよ、いかに付加価値を生み出すかを考え、利益が出る仕組みを作らなければなりません。そう簡単に濡れ手で粟はない、という当たり前の話かと思います。

今よく言われる「加工品作りに手を出すくらいなら栽培に力を入れた方がいい」は、「プラスアルファで利益を出すために加工品作りに手を出すくらいならノウハウのある栽培に力を入れた方が勝率がいい」ということなのかなと思っています。

一方で、それでも尚、6次化(加工品作り)をしっかりと農業経営に組み込むことには一定の価値があると考えています。特に経営の安定化という面では大きなポテンシャルを感じます。


ということで、思うところを書いてみました。何も調べていないので、事実と異なる認識がある可能性もあります。あしからず。


ピクルス

2024年2月5日月曜日

農業者による農業者のためのお金の話

最近、新規就農された方(研修中含む)と税金の申告や年金、保険、補助金といったお金まわりのことについて話す機会がありました。

やはり、お金のこととなると気軽に相談できる機会も限られるようで、どうしたらいいのか分からない部分がままあるようです。

そんなわけで、あくまでも1サンプルとしてですが、私の場合を簡単に書いてみようかと思います。(個人経営体で、法人化はしていません)

誰かの参考になれば幸いです。

(私の認識が間違っていることもありますので、鵜呑みにせず、ご自身でしっかり調べるようにしください)


【確定申告】
ちゃんとやる。

まだ収入がない場合も、住民税の申告はちゃんとやる(→国民健康保険の軽減を受けるため)。

農業の収入が発生したら青色申告をする。要件を満たし65万円の控除を受ける。経営の状態をしっかり把握する。


【国民健康保険】
ちゃんと払う。

(世帯)収入が少ない場合は(世帯全員が)住民税の申告をしていれば軽減される。


【年金】
国民年金はちゃんと払う。払っていれば終身の老齢基礎年金だけでなく、万が一の場合には障害年金や遺族年金が受給できる。

払えないときは、確実に免除申請をする。免除申請をすれば受給資格を得られる。保険料の半分は税金による負担なので未納はただただ損。

増額手段は付加年金、農業者年金、iDeCo(個人型確定拠出年金)、国民年金基金、小規模企業共済から選択。それぞれ掛金の税制(社会保険料控除 or 小規模企業共済等掛金控除)や受給の仕方が異なるので、家族構成や年齢、働き方、好み等に合わせて検討。

私は、付加年金を納付の上で、iDeCoに加入しています。ただし、現状NISA(少額投資非課税制度)への積み立てを優先しているため(理由後述)、iDeCoの掛金は最低額の5,000円にしています。

iDeCoは、加入年数によって一時金として受給する際の退職所得控除の額が決まるので、最低額からでも早めに加入した方が有利です。NISAの枠が(いつの日か)埋まったら、もしくは資金に相当な余裕ができたら退職所得控除の額と調整して掛金を増額予定です。


・参考


【NISA】
今年から始まった新しいNISAはぜひ活用したいところです。枠も従来より大幅に大きくなり、将来への積立の目標値としてちょうどいいです。皮算用ではありますが、毎月5万円を年5%の複利運用で30年間積み立てると約4,000万円になります。

iDeCoは掛金が所得控除の対象となる点でNISAより有利ですが、私は突然の資金需要への対策としてNISAを優先しています。


【保険】
青色申告をして農業共済の収入保険に入る。その他、事業内容に応じて適宜入る。

さわの場合は、水稲関連の農機具共済や農産物加工をやっているのでPL保険に入っています。その他、個人として自動車保険、火災保険、個人賠償保険は必須。

生命保険や医療保険等についても、自身の状況に合わせて検討。ただし、こちらは一般論として入りすぎに注意。どういう状況に備えるものかを明確にすることが変な保険に入らないためのコツ。

ちなみに、保険に関してはネットで入れるものが良いとも限らず、代理店経由じゃないと加入できないものもあります。

・個人的に参考になった情報
センパイちゃんねる(YouTube チャンネルですが、保険についての冷静な考え方がとても参考になります)


【補助金】
活用できる補助金は活用。ただし、将来の選択肢を制限されることもあるので取捨選択は必要。また補助金ありきで事業を作ると長続きしない(個人の印象)。

私の場合、加工場を作るために市の補助金を利用しましたが、青年就農給付金(現在の農業次世代人材投資資金)については、撤退の判断がしづらくなるという理由で利用しませんでした。


【借入金】
以前は、農林水産省が出している「新たな農業経営指標」の借入金に関する2つの指標を目安にしていましたが、いつの間にかこの指標は使われなくなった(?)ようです。ページがまるっと消えていました。まあ、新たなと言いつつ、すでに10年以上前に作られたものですからね。

参考までに、指標では以下のようになっていました。

・売上高借入金比率
 計算式:借入金/収入 計
 望ましい水準:0~100%

・生産単位当たり借入金
 計算式:借入金/耕作している農地面積 合計
 望ましい水準:0~130 千円/10a

借入金に関する指標を2つとも満たすには、10a当たりの売上高が13万円より多いのか、少ないのかにより、どちらの望ましい水準の上限がキャップになるかを把握する必要があります。

多ければ「生産単位当たり借入金」の上限が、少なければ「売上高借入金比率」の上限がキャップになります。


【番外 - クラウドファンディング】
支援者の立場での感想として、公共性の高いものや、Kickstarter 的なプロジェクトは支援しやすいです。もしくは、本当に寄付として、というもの。

一方で、資金の使い道にはてなが浮かぶようなものは関わらない方がいいのかなと。あとは、素直に応援の気持ちで支援したものでも、その後の活動や報告がないと、どうしても微妙な気持ちにはなりますね。


***

独立して農業をやっていくにあたっては、まずは何より生活基盤を固めることが肝要かと思います。

具体的には、「生活費+備え(保険や積立等)」で年間どのくらいの金額が必要なのかを把握して、その上で経営計画を立てるということですね。

仕事ですから、結局はどのくらいの労働力を投入し、どのくらいのお金の出入りを目指すのかということになります。その実現に向けて、最適な規模はどの程度のものなのか。ここがピタッと見極められると、安定感が出てくるんじゃないかと思います。


雪だるま猫