マネーフォワード MEの家計簿データをCSVダウンロードしてPythonで分析してみる

このnoteでは、プログラミング言語Pythonを書ける方向けに、ご自身のマネーフォワード MEの家計簿データをCSVダウンロードして分析する方法をご紹介します。

マネーフォワード MEのプレミアムサービスを利用されている方は、マネーフォワード MEのWebページのログイン後の「家計簿」タブ→「入出金」タブ→「家計簿データの出力(Excel、CSV形式対応)」→「ダウンロード」ボタンから、入出金の収入・支出詳細をダウンロードできます。

この記事の対象読者

上記いずれも当てはまる方です。

CSVデータを読み込む

ダウンロードしたCSVファイルは「収入・支出詳細_yyyy-mm-01_yyyy-mm-dd.csv」というファイル名になっています(ddは月末日付)。作業ディレクトリの下に「mfmedata」というディレクトリを作り、ダウンロードしたCSVファイルをすべて保存しましょう。

path = './mfmedata/'

すべてのCSVファイルを読み込んで結合する関数を用意します。

import os
import pandas as pd

def union_mfme_csv(path, to_csv=False):
    if path[-1] != '/':
        path = path + '/'

    # ディレクトリに保存してあるCSVファイル名をリストに格納
    file_list = []
    for file_name in os.listdir(path):
        if (file_name[-4:] == '.csv') & (file_name[:7] == '収入・支出詳細'):
            file_list += [file_name]

    # CSVファイルの読み込み
    if len(file_list) != 0:
        dfs = []
        for file_name in file_list:
            # utf-8で読み込み
            try:
                df_tmp = pd.read_csv(path+file_name)
                df_tmp['ファイル名'] = file_name
                dfs += [df_tmp]
                print(file_name, ': utf-8')
            except:
                # shif-jisで読み込み
                try:
                    df_tmp = pd.read_csv(path+file_name, encoding='shift-jis')
                    df_tmp['ファイル名'] = file_name
                    dfs += [df_tmp]
                    print(file_name, ': shift-jis')
                except:
                    # cp932で読み込み
                    try:
                        df_tmp = pd.read_csv(path+file_name, encoding='cp932')
                        df_tmp['ファイル名'] = file_name
                        dfs += [df_tmp]
                        print(file_name, ': cp932')
                    except:
                        print(file_name, ' は utf-8, shift-jis, cp932のいずれでも読み込めません。')
                        pass

        # 読み込んだDataFrameの結合
        if len(dfs) == 1:
            df = dfs
        else:
            df = pd.concat(dfs, axis=0)
        # 日付をTimeStampに変換し並び替え
        df['日付'] = pd.to_datetime(df['日付'])
        df = df.sort_values('日付')

        # CSVファイルに出力
        if to_csv:
            df.to_csv(path+'uniondata/mf_収入・支出詳細_'+df['日付'].min().strftime('%Y-%m-%d')+'_'+df['日付'].max().strftime('%Y-%m-%d')+'.csv')
        return df
    
    else:
        print('収入・支出詳細のCSVファイルが見つかりません。')
        pass

df = union_mfme_csv(path, to_csv=True)

utf-8, shift-jis, cp932のいずれでも読み込めないCSVファイルは「内容」列に何らかの特殊文字列が入っている可能性が高いため、その文字列を取り除きましょう。

積み上げ棒グラフで支出の時系列推移を可視化する

毎年の支出を時系列の積み上げ棒グラフで可視化します。まず、各種関数を用意します。

# 計算対象のみに絞る関数
def extract_culc_obj(df):
    return df[df['計算対象'] == 1]

# 支出をプラスに変換して抽出する関数
def extract_expenditure(df):
    df.loc[:, '金額(円)'] = - df['金額(円)']
    return df[df['金額(円)'] > 0]

# 収入を抽出する関数
def extract_income(df):
    return df[df['金額(円)'] > 0]

# 日付と大項目・中項目でクロス集計する関数
def crosstab_date_category(df, resample_rule='A'):
    cols = ['日付', '大項目', '中項目']
    crosstab = df[cols+['金額(円)']].groupby(cols).sum()
    output = crosstab.unstack(level=[1, 2]).resample(resample_rule).sum().stack(level=[1, 2]).reset_index()
    return output

# 計算対象や収入・支出を絞った上で、日付と大項目・中項目でクロス集計する関数
def crosstab_mfdata(df, resample_rule='A', income_or_expenditure='支出'):
    # 日付をTimestampに変換
    if df['日付'].dtype is not pd._libs.tslibs.timestamps.Timestamp:
        df['日付'] = pd.to_datetime(df['日付'])
    # 日付と大項目・中項目でクロス集計
    if income_or_expenditure == '収入':
        output = crosstab_date_category(extract_income(extract_culc_obj(df)), resample_rule=resample_rule)
    else:
        output = crosstab_date_category(extract_expenditure(extract_culc_obj(df)), resample_rule=resample_rule)
    return output

毎年の各支出の時系列データを作成します。

income_or_expenditure = '支出'  # 支出または収入
rr = 'A'  # A: 年間, Q: 四半期, M: 月次
crosstab = crosstab_mfdata(df, resample_rule=rr, income_or_expenditure=income_or_expenditure)
crosstab

plotlyで可視化します。

import plotly
import plotly.graph_objects as go

def timeseries_bar(data_frame, cat='中項目', display_axis=True):
    traces = []
    for i in data_frame[cat].unique():
        df_tmp = data_frame[data_frame[cat]==i][['日付', '金額(円)']]
        traces += [go.Bar(x=df_tmp['日付'], y=df_tmp['金額(円)'], name=i)]

    layout = go.Layout(
        barmode='stack',
        width=1000,
        height=600,
    )
    fig = go.Figure(
        data=traces,
        layout=layout
    )
    # 金額を表示する場合
    if display_axis:
        fig.update_layout(
            xaxis = {'ticksuffix': '年'},
            yaxis = { 
                'tickformat': ',.0f',
                'ticksuffix': '円'
            }
        )
    else:
        fig.update_layout(
            xaxis = {'ticksuffix': '年'},
        )
        fig.update_yaxes(showticklabels=False)
    return fig

fig = timeseries_bar(crosstab, cat='大項目', display_axis=False)
fig.show()

さすがに、可視化した個人情報をお見せすることはできないので悪しからず。。一応、display_axis=Falseで、数値を表示しない設定にできるようにはしています。

ツリーマップで支出の内訳を可視化する

マネーフォワード MEのアプリでは円グラフ(ドーナツチャート)で内訳が可視化されていますが、ここではツリーマップで特定期間の支出の内訳を可視化してみます。まず、特定の期間を抽出する関数を用意します。

# 特定の年を抽出する関数
def extract_year(df, year=None, start_year=None, end_year=None):
    cond = df['日付'].notnull()
    if year:
        cond &= df['日付'].dt.year == year
    if start_year:
        cond &= df['日付'].dt.year >= start_year
    if end_year:
        cond &= df['日付'].dt.year <= end_year
    return df[cond]

plotly.expressで可視化する関数を用意します。

import plotly.express as px

def expenditure_treemap(data_frame,
        textinfo_value=True,
        textinfo_percent_root=True,
        textinfo_percent_parent=True
    ):
    fig = px.treemap(
        data_frame, 
        path=['大項目', '中項目'], 
        values='金額(円)'
    )
    
    textinfo_lst = ["label"]
    if textinfo_value:
        textinfo_lst += ["value"]
    if textinfo_percent_root:
        textinfo_lst += ["percent root"]
    if textinfo_percent_parent:
        textinfo_lst += ["percent parent"]
    fig.data[0].textinfo = "+".join(textinfo_lst)
    
    fig.update_layout(width=1120, height=630)
    
    return fig

可視化用のテーブルを用意します。

treemap_df = crosstab_date_category(extract_expenditure(extract_year(extract_culc_obj(df), start_year=2018,end_year=2022)), resample_rule='A')
treemap_df

ツリーマップを可視化します

fig = expenditure_treemap(treemap_df,
        textinfo_value=False,
        textinfo_percent_root=False,
        textinfo_percent_parent=False)
fig.show()

こちらもtextinfo_value=Falseで、数値を表示しない設定にできるようにしています。

積み上げ棒グラフとツリーマップで可視化しましたが、可視化前のcrosstabを使って将来をシミュレーションしてみるなどご活用ください。

おわりに

今回の記事は、執筆者のただの趣味ですが、一応、執筆者はマネーフォワードに所属しています。
マネーフォワードのデータ組織にご興味のある方は、よろしければ「マネーフォワード・データ&AI」マガジンもご覧ください。


いいなと思ったら応援しよう!