🙆

Pythonでジャーナリングアプリを作成してみた

2025/01/07に公開

はじめに

日々の忙しさの中で、自分自身の感情や経験を振り返る時間を取ることは難しいものです。しかし、こうした「内省」の時間は、ストレスの軽減や自己成長において重要な役割を果たします。このジャーナリングアプリは、そのような内省の習慣を手軽に、そして楽しく始められるツールとして開発されました。日常の出来事や感情を記録し、直感的な視覚化を通じて自分を振り返る新しい方法を提供します。

解決したい社会課題

現代社会では、ストレスやメンタルヘルスの問題が深刻化しています。特に、忙しい日常の中で自分の感情に気づくことができず、心の疲れを放置してしまう人が多い現状があります。これらの問題に対処するためには、自己理解や感情の整理が重要です。
このジャーナリングアプリは、感情や出来事を記録することで自分の状態に気づき、問題の早期発見やメンタルヘルスの向上を支援することを目指しています。また、記録をデータとして可視化することで、自分の状態をより客観的に把握する手助けを行います。

実行環境

パソコン:Windows 10
開発環境:VScode
言語:Python

実行したコード

  1. ToDoリスト・日々の小さな目標を記入して追加、達成出来たらチェックをつけられるようにしました。また、過去の達成記録も表示されるようにしました。
# ToDoリストページ
elif page == "ToDoリスト":
    st.header("今日のToDoリスト")

    # 今日の日付
    today_date = datetime.now().strftime('%Y-%m-%d')

    # CSVファイルからToDoリストを読み込む関数
    def load_todos():
        todos = []
        try:
            with open('ToDo_checklist.csv', mode='r', newline='', encoding='utf-8') as file:
                reader = csv.reader(file)
                header = next(reader)  # ヘッダーをスキップ
                for row in reader:
                    todos.append({
                        'date': row[0],
                        'task': row[1],
                        'completed': bool(int(row[2]))
                    })
        except FileNotFoundError:
            st.warning("ToDoリストデータが見つかりません。新しい目標を追加するとデータが作成されます。")
        return todos

    # セッション状態にToDoリストがなければ初期化し、CSVから読み込む
    if "todos" not in st.session_state:
        st.session_state.todos = load_todos()

    # 新しいToDoを入力するテキストエリア
    new_todo = st.text_area("今日の目標を入力してください。", "")

    # Addボタン
    if st.button("Add"):
        if new_todo:
            st.session_state.todos.append({
                "date": today_date,
                "task": new_todo,
                "completed": False
            })
            st.experimental_rerun()
        else:
            st.warning("目標を入力してください。")

    # 今日のToDoリストのチェックボックスを表示
    st.subheader(f"{today_date} のToDoリスト")
    for i, todo in enumerate(st.session_state.todos):
        if todo["date"] == today_date:
            task = todo["task"]
            completed = st.checkbox(task, value=todo["completed"], key=f"todo_{i}")
            st.session_state.todos[i]["completed"] = completed

    # Sendボタン
    if st.button("Send"):
        if len(st.session_state.todos) > 0:
            with open('ToDo_checklist.csv', mode='w', newline='', encoding='utf-8') as file:
                writer = csv.writer(file)
                writer.writerow(['date', 'task', 'completed'])
                for todo in st.session_state.todos:
                    row = [
                        todo["date"],
                        todo["task"],
                        1 if todo["completed"] else 0
                    ]
                    writer.writerow(row)

            st.experimental_rerun()  # 入力ボックスをクリアするために再レンダリング
        else:
            st.warning("目標がありません。")

    # 過去のToDoリストを表示 (直近の10個のみ、新しいものを上に)
    st.header("過去のToDoリスト")

    # ローカル画像のパスまたはURLを指定
    image_path = "pic.jpg"  # 画像ファイルを指定
    # 画像を表示
    st.image(image_path, caption="", use_column_width=True)

    past_todos = [todo for todo in st.session_state.todos if todo["date"] != today_date]
    if len(past_todos) > 0:
        for todo in reversed(past_todos[-10:]):  # 逆順に直近10件を表示
            completed_str = "✔️" if todo["completed"] else "❌"
            st.markdown(
                f"""
                <div style="border: 1px solid #ddd; padding: 10px; border-radius: 5px; margin-bottom: 10px; white-space: pre-wrap;">
                    <span style="font-size: 1.2em; font-weight: bold;">{todo['task']}</span><br> <!-- フォントサイズを大きく -->
                    <span style="font-size: 1em; color: #888;">{todo['date']}</span><br>
                    <span style="font-size: 1em; color: #888;">{completed_str}</span>
                </div>
                """,
                unsafe_allow_html=True
                )
        else:
            st.info("まだToDoはありません。")

  1. 起きた出来事、その時思い浮かんだことを書き込めるようにしました。また、その時の感情を選択し、直近10件の入力項目を表示できるようにして、そこからワードクラウドを作成できるようにしました。感情は以下の項目から選択できるようにしました。
 "Anger-Hostility", "Confusion-Bewilderment","Depression-Dejection", "Fatigue-Inertia",
 "Tension-Anxiety", "Vigor-Activity", "Friendliness"
# CSVファイルから最新の10件のつぶやきを読み込む関数
def load_recent_tweets():
    tweets = []
    try:
        with open('facts_and_feelings.csv', mode='r', newline='', encoding='utf-8') as file:
            reader = csv.reader(file)
            header = next(reader)  # ヘッダーをスキップ
            for row in reader:
                tweet = {
                    'date': row[0],
                    'facts and feelings': row[1],
                    'moods': [header[i] for i in range(2, len(header)) if row[i] == '1']  # '1'の列をmoodsとして読み込む
                }
                tweets.append(tweet)
                if len(tweets) > 10:
                    tweets.pop(0)  # 最新の10件だけ保持
    except FileNotFoundError:
        st.warning("つぶやきデータが見つかりません。初めてつぶやきをするとデータが作成されます。")
    
    return tweets

# CSVファイルから最新の10件のToDoを読み込む関数
def load_recent_todos():
    todos = []
    try:
        with open('ToDo_checklist.csv', mode='r', newline='', encoding='utf-8') as file:
            reader = csv.reader(file)
            header = next(reader)  # ヘッダーをスキップ
            for row in reader:
                todo = {
                    'date': row[0],
                    'task': row[1],
                    'completed': row[2] == '1'
                }
                todos.append(todo)
                if len(todos) > 10:
                    todos.pop(0)  # 最新の10件だけ保持
    except FileNotFoundError:
        st.warning("ToDoデータが見つかりません。初めてToDoを追加するとデータが作成されます。")
    
    return todos

# セッション状態につぶやきリストがなければ初期化し、CSVから最新の10件を読み込む
if "tweets" not in st.session_state:
    st.session_state.tweets = load_recent_tweets()

# セッション状態にToDoリストがなければ初期化し、CSVから最新の10件を読み込む
if "todos" not in st.session_state:
    st.session_state.todos = load_recent_todos()




# ------------------------------------------------------------------------------------------------
# タイムラインページ
if page == "タイムライン":
    # 新しいつぶやきとムードを入力するテキストエリアとセレクトボックス
    if "new_tweet" not in st.session_state:
        st.session_state.new_tweet = ""
    if "moods" not in st.session_state:
        st.session_state.moods = []

    new_tweet = st.text_area("The facts and feelings as they exist.", st.session_state.new_tweet)
    moods = st.multiselect("current feelings", [
        "Anger-Hostility",
        "Confusion-Bewilderment",
        "Depression-Dejection",
        "Fatigue-Inertia",
        "Tension-Anxiety",
        "Vigor-Activity",
        "Friendliness"
    ], default=st.session_state.moods)

    # Sendボタン
    if st.button("Send"):
        if new_tweet and moods:
            current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            new_entry = {
                "date": current_time,
                "facts and feelings": new_tweet,
                "moods": moods
            }
            
            # セッションに新しいつぶやきを追加
            st.session_state.tweets.append(new_entry)
            
            # 10件を超えた場合、古いつぶやきを削除
            if len(st.session_state.tweets) > 10:
                st.session_state.tweets.pop(0)
            
            # CSVファイルに保存
            with open('facts_and_feelings.csv', mode='a', newline='', encoding='utf-8') as file:
                writer = csv.writer(file)
                if file.tell() == 0:  # ファイルが空の場合はヘッダーを書き込む
                    writer.writerow(['date', 'facts and feelings', 'Anger-Hostility', 'Confusion-Bewilderment', 'Depression-Dejection', 'Fatigue-Inertia', 'Tension-Anxiety', 'Vigor-Activity', 'Friendliness'])
                row = [
                    current_time,
                    new_tweet
                ]
                mood_values = [1 if mood in moods else 0 for mood in [
                    "Anger-Hostility",
                    "Confusion-Bewilderment",
                    "Depression-Dejection",
                    "Fatigue-Inertia",
                    "Tension-Anxiety",
                    "Vigor-Activity",
                    "Friendliness"
                ]]
                row.extend(mood_values)
                writer.writerow(row)

            st.session_state.new_tweet = ""  # 入力ボックスをクリア
            st.session_state.moods = []  # ムードをクリア

            st.experimental_rerun()  # 入力ボックスをクリアするために再レンダリング
        elif not new_tweet:
            st.warning("つぶやき内容が空です。")
        elif not moods:
            st.warning("ムードを選択してください。")

    # 過去のつぶやきを表示 (直近の10個のみ、新しいものを上に)
    st.header("あなたのタイムライン")

    if len(st.session_state.tweets) > 0:
        for tweet in reversed(st.session_state.tweets):  # 逆順に表示
            moods_str = ', '.join(tweet['moods']) if 'moods' in tweet else ''
            st.markdown(
                f"""
                <div style="border: 1px solid #ddd; padding: 10px; border-radius: 5px; margin-bottom: 10px; white-space: pre-wrap;">
                    {tweet['facts and feelings']}<br>
                    <span style="font-size: 0.8em; color: #888;">{tweet['date']}</span><br>
                    <span style="font-size: 0.8em; color: #888;">Moods: {moods_str}</span>
                </div>
                """,
                unsafe_allow_html=True
            )
    else:
        st.info("まだつぶやきはありません。")
    
    # ワードクラウドの生成と表示
    st.header("ワードクラウド")

    # 直近10件のつぶやき内容を結合
    recent_tweets_text = " ".join([tweet['facts and feelings'] for tweet in st.session_state.tweets])

    # 選択されたムードのワードを除外
    selected_moods = ["Anger-Hostility", "Confusion-Bewilderment", "Depression-Dejection", "Fatigue-Inertia", "Tension-Anxiety", "Vigor-Activity", "Friendliness"]
    for mood in selected_moods:
        recent_tweets_text = recent_tweets_text.replace(mood, "")

    # ワードクラウドの生成
    wordcloud = WordCloud(width=800, height=400, background_color='white', colormap='viridis').generate(recent_tweets_text)

    # ワードクラウドの表示
    fig, ax = plt.subplots()
    ax.imshow(wordcloud, interpolation='bilinear')
    ax.axis("off")
    st.pyplot(fig)
  1. 選択した感情の変化を目視できるようにグラフ化しました。1日に何回感情の選択が行われたか、また、最新の1件(1日)を円グラフにして表示させました。
# 感情の変化ページ
elif page == "感情の変化":
    st.header("感情の変化")

    # 感情の変化を可視化するためのデータフレームを作成
    def create_mood_dataframe():
        try:
            df = pd.read_csv('facts_and_feelings.csv', encoding='utf-8')
            df['date'] = pd.to_datetime(df['date']).dt.date  # 日付を抽出
            return df
        except FileNotFoundError:
            return pd.DataFrame(columns=['date', 'Anger-Hostility', 'Confusion-Bewilderment', 'Depression-Dejection', 'Fatigue-Inertia', 'Tension-Anxiety', 'Vigor-Activity', 'Friendliness'])

    df = create_mood_dataframe()

    if not df.empty:
        # 感情ごとに日付ごとの合計値を計算
        moods = ["Anger-Hostility", "Confusion-Bewilderment", "Depression-Dejection", "Fatigue-Inertia", "Tension-Anxiety", "Vigor-Activity", "Friendliness"]
        daily_mood_counts = df.groupby('date')[moods].sum().reset_index()

        # データフレームを長形式に変換
        daily_mood_counts_long = pd.melt(daily_mood_counts, id_vars='date', var_name='Mood', value_name='Count')

        # 色指定を追加
        color_map = {
            "Anger-Hostility": "lightcoral",
            "Confusion-Bewilderment": "deepskyblue",
            "Depression-Dejection": "plum",
            "Fatigue-Inertia": "palegreen",
            "Tension-Anxiety": "yellow",
            "Vigor-Activity": "cyan",
            "Friendliness": "magenta"
        }

        # 積み上げ棒グラフの作成
        fig = px.bar(
            daily_mood_counts_long,
            x='date',
            y='Count',
            color='Mood',
            color_discrete_map=color_map,  # 色の指定
            title='一日の感情のカウント',
            labels={'date': '日付', 'Count': '感情のカウント', 'Mood': '感情'},
            text='Count',
        )

        fig.update_layout(
            barmode='stack',
            xaxis_title="日付",
            yaxis_title="感情のカウント",
            margin=dict(l=40, r=20, t=40, b=20)
        )

        st.plotly_chart(fig, use_container_width=True)

        # 直近1件のデータを取得
        latest_date = df['date'].max()
        latest_data = df[df['date'] == latest_date][moods].sum().reset_index()
        latest_data.columns = ['Mood', 'Count']

        # 円グラフの作成
        pie_chart = px.pie(
            latest_data,
            names='Mood',
            values='Count',
            title=f"{latest_date} の感情の割合",
            color='Mood',
            color_discrete_map=color_map  # 同じ色を使用
        )

        st.plotly_chart(pie_chart, use_container_width=True)

    else:
        st.info("まだつぶやきはありません。")

実行結果


2.


3.

課題

現代社会では、時間に追われる生活や情報の洪水によって、自分の感情や日々の出来事を振り返る余裕が失われがちです。その結果、多くの人がストレスを溜め込んだり、自己理解が不足してモチベーションの低下や人間関係の摩擦を招くことがあります。また、忙しさの中で小さな成功や感謝の瞬間を見過ごしてしまうことで、ポジティブな気持ちを維持することが難しくなるという課題も存在します。

感情や出来事の記録が継続できない理由の一つに、「書くことが面倒」「振り返り方が分からない」「どのように活用すればいいか不明」という声があります。これらのハードルを下げ、日常生活の中で自然に内省の時間を作り出せる仕組みを作れるようにしたいと思いました。

まとめ

ジャーナリングアプリを作成しました。

Discussion