├── data └── example.csv ├── .gitattributes ├── requirements.txt ├── assets ├── FocusPandas bento.png └── FocusPandas header.png ├── utilities ├── inspect_db.py └── extract_db.py ├── LICENSE ├── .gitignore ├── README.md ├── brain_rot.py ├── roi.py ├── categories.py └── app.py /data/example.csv: -------------------------------------------------------------------------------- 1 | app,usage,start_time,end_time,created_at,tz,device_id,device_model -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | streamlit 3 | matplotlib 4 | plotly 5 | numpy 6 | xmltodict 7 | seaborn 8 | pydantic -------------------------------------------------------------------------------- /assets/FocusPandas bento.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasInk/FocusPandas/HEAD/assets/FocusPandas bento.png -------------------------------------------------------------------------------- /assets/FocusPandas header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndreasInk/FocusPandas/HEAD/assets/FocusPandas header.png -------------------------------------------------------------------------------- /utilities/inspect_db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | 4 | # Path to the Screen Time database 5 | db_path = os.path.expanduser('~/Library/Application Support/Knowledge/knowledgeC.db') 6 | 7 | # Connect to the SQLite database 8 | conn = sqlite3.connect(db_path) 9 | 10 | # Query to get the schema of all tables 11 | schema_query = "SELECT name FROM sqlite_master WHERE type='table';" 12 | tables = conn.execute(schema_query).fetchall() 13 | 14 | print("Tables in the database:") 15 | for table in tables: 16 | print(table[0]) 17 | columns_query = f"PRAGMA table_info({table[0]});" 18 | columns = conn.execute(columns_query).fetchall() 19 | for column in columns: 20 | print(f" {column[1]} ({column[2]})") 21 | 22 | conn.close() 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Andreas Ink 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /utilities/extract_db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import pandas as pd 3 | import os 4 | from datetime import datetime 5 | # Path to the Screen Time database 6 | db_path = os.path.expanduser('~/Library/Application Support/Knowledge/knowledgeC.db') 7 | 8 | # Convert Unix timestamp to human-readable date 9 | def unix_to_date(unix_timestamp): 10 | try: 11 | return datetime.fromtimestamp(unix_timestamp).strftime('%Y-%m-%d %H:%M:%S') 12 | except (OSError, OverflowError, ValueError): 13 | return None 14 | 15 | # Check if the database file exists 16 | if not os.path.exists(db_path): 17 | print(f"Database file not found at {db_path}") 18 | else: 19 | try: 20 | # Connect to the SQLite database 21 | conn = sqlite3.connect(db_path) 22 | 23 | # Provided query 24 | query = """ 25 | SELECT 26 | ZOBJECT.ZVALUESTRING AS "app", 27 | (ZOBJECT.ZENDDATE - ZOBJECT.ZSTARTDATE) AS "usage", 28 | (ZOBJECT.ZSTARTDATE + 978307200) as "start_time", 29 | (ZOBJECT.ZENDDATE + 978307200) as "end_time", 30 | (ZOBJECT.ZCREATIONDATE + 978307200) as "created_at", 31 | ZOBJECT.ZSECONDSFROMGMT AS "tz", 32 | ZSOURCE.ZDEVICEID AS "device_id", 33 | ZSYNCPEER.ZMODEL AS "device_model" 34 | FROM 35 | ZOBJECT 36 | LEFT JOIN 37 | ZSTRUCTUREDMETADATA 38 | ON ZOBJECT.ZSTRUCTUREDMETADATA = ZSTRUCTUREDMETADATA.Z_PK 39 | LEFT JOIN 40 | ZSOURCE 41 | ON ZOBJECT.ZSOURCE = ZSOURCE.Z_PK 42 | LEFT JOIN 43 | ZSYNCPEER 44 | ON ZSOURCE.ZDEVICEID = ZSYNCPEER.ZDEVICEID 45 | WHERE 46 | ZSTREAMNAME = "/app/usage" 47 | ORDER BY 48 | ZSTARTDATE DESC 49 | """ 50 | 51 | # Load the data into a pandas DataFrame 52 | df = pd.read_sql_query(query, conn) 53 | 54 | # Print the first few rows of the DataFrame to verify data 55 | print("Initial data loaded:") 56 | print(df.head()) 57 | 58 | # Convert Unix timestamps to human-readable dates 59 | df['start_time'] = df['start_time'].apply(unix_to_date) 60 | df['end_time'] = df['end_time'].apply(unix_to_date) 61 | df['created_at'] = df['created_at'].apply(unix_to_date) 62 | 63 | # Print the data after date conversion 64 | print("Data after date conversion:") 65 | print(df.head()) 66 | 67 | # Close the database connection 68 | conn.close() 69 | 70 | # Remove rows with invalid dates 71 | df = df.dropna(subset=['start_time', 'end_time', 'created_at']) 72 | 73 | # Print the data before exporting 74 | print("Final data before exporting:") 75 | print(df.head()) 76 | 77 | # Save to CSV 78 | csv_path = f'../data/screentime_data_{datetime.now()}.csv' 79 | df.to_csv(csv_path, index=False) 80 | print(f"Screen Time data has been exported to {csv_path}") 81 | 82 | # Save to JSON 83 | json_path = f'../data/screentime_data_{datetime.now()}.json' 84 | df.to_json(json_path, orient='records', lines=True) 85 | print(f"Screen Time data has been exported to {json_path}") 86 | 87 | except sqlite3.OperationalError as e: 88 | print(f"An error occurred: {e}") 89 | except AttributeError as e: 90 | print(f"AttributeError: {e}") 91 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | *.DS_Store 162 | 163 | /data/ 164 | *.csv 165 | *.xml 166 | *.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FocusPandas: HealthKit and Screen Time Data Analyzer 2 | 3 | ![FocusPandas Header](https://github.com/AndreasInk/FocusPandas/blob/main/assets/FocusPandas%20header.png) 4 | 5 | ![FocusPandas Bento](https://github.com/AndreasInk/FocusPandas/blob/main/assets/FocusPandas%20bento.png) 6 | 7 | **FocusPandas** is a data visualization and analysis tool that helps you explore and understand your digital habits, health metrics, and productivity patterns. Using Apple Health and Screen Time data, FocusPandas provides insights into how your daily habits influence your productivity, health, and overall well-being. 8 | 9 | ## Features 10 | - **Screen Time Analysis**: Explore app usage trends, peak usage times, and detailed app statistics. Use this tool to preserve your Screen Time data, ensuring it remains available for long-term analysis, even if your system resets it. 11 | - **Heart Rate Insights**: Correlate heart rate data with screen time and productivity. 12 | - **Sleep Analysis**: Explore the relationship between sleep patterns, productivity, and screen time. 13 | - **Productivity Metrics**: Calculate daily productivity ratios and highlight trends. 14 | - **Additional Insights**: Gain insights into audio exposure, digital habits, and more. 15 | 16 | 17 | --- 18 | 19 | ## Getting Started 20 | 21 | Follow these steps to set up and run the application: 22 | 23 | ### 1. Clone the Repository 24 | ```bash 25 | git clone https://github.com/AndreasInk/FocusPandas.git 26 | cd focuspandas 27 | ``` 28 | 29 | ### 2. Set Up a Virtual Environment 30 | Create and activate a virtual environment to manage dependencies: 31 | ```bash 32 | python3 -m venv venv 33 | source venv/bin/activate # On Windows: venv\Scripts\activate 34 | ``` 35 | 36 | ### 3. Install Dependencies 37 | Install the required Python libraries using `requirements.txt`: 38 | ```bash 39 | pip install -r requirements.txt 40 | ``` 41 | 42 | ### 4. Extract Screen Time Data 43 | Run the following command to extract Screen Time data into the `data/` directory: 44 | ```bash 45 | sudo python3 extract_db.py 46 | ``` 47 | This step fetches the Screen Time data from macOS's database and saves it locally, ensuring you have a backup of your digital habits. This is especially useful as macOS only retains Screen Time data for up to 2 months or may reset data during OS updates. 48 | 49 | ### 5. Export Apple Health Data 50 | Export your Apple Health data from the **Apple Health** app: 51 | 1. Open the Apple Health app on iOS. 52 | 2. Go to your profile (top right of the app on iOS 18+). 53 | 3. Tap **Export All Health Data** (at the bottom of the view). 54 | 4. Place the resulting XML file in the `data/` directory. 55 | 56 | ### 6. Run the Streamlit App 57 | Launch the app using Streamlit: 58 | ```bash 59 | streamlit run app.py 60 | ``` 61 | The app will open in your default web browser. If not, visit [http://localhost:8501](http://localhost:8501). 62 | 63 | --- 64 | 65 | ## Application Overview 66 | 67 | The app is organized into six main sections, accessible from the sidebar: 68 | 1. **Screen Time Analysis** 69 | Explore app usage trends, peak usage times, and detailed app statistics. 70 | 2. **Heart Rate Analysis** 71 | Analyze how heart rate correlates with screen time and digital habits. 72 | 3. **Sleep Analysis** 73 | Visualize sleep duration trends and how sleep affects productivity and screen usage. 74 | 4. **Productivity Analysis** 75 | Understand the relationship between productivity, sleep, and audio exposure. 76 | 5. **Productivity Metrics** 77 | View daily productivity metrics, including usage hours and productivity ratios. 78 | 6. **Additional Insights** 79 | Gain insights into daily screen time patterns, productivity vs. audio exposure, and more. 80 | 81 | --- 82 | 83 | ## Data Sources 84 | - **Screen Time Data**: Extracted directly from your system database. 85 | - **Apple Health Data**: Exported via Apple Health and processed to extract heart rate, sleep, and audio exposure metrics. 86 | 87 | --- 88 | 89 | ## Dependencies 90 | 91 | The project uses the following Python libraries: 92 | - `streamlit` for creating the interactive web app. 93 | - `pandas` for data processing and analysis. 94 | - `matplotlib` for data visualization. 95 | - `pydantic` for managing structured data. 96 | 97 | --- 98 | 99 | ## License 100 | 101 | This project is licensed under the MIT License. See the `LICENSE` file for details. 102 | 103 | --- 104 | 105 | ## Contributing 106 | 107 | Contributions would be great! Please feel free to submit a pull request or open an issue. 108 | -------------------------------------------------------------------------------- /brain_rot.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import xml.etree.ElementTree as ET 3 | import pandas as pd 4 | import matplotlib.pyplot as plt 5 | import plotly.express as px 6 | 7 | 8 | # Function to parse HealthKit export.xml 9 | def parse_healthkit_export(xml_file): 10 | tree = ET.parse(xml_file) 11 | root = tree.getroot() 12 | 13 | # Extract relevant health metrics 14 | health_data = [] 15 | for record in root.findall("Record"): 16 | if record.attrib.get('type') in [ 17 | "HKQuantityTypeIdentifierHeartRate", 18 | "HKQuantityTypeIdentifierHeartRateVariabilitySDNN", 19 | "HKQuantityTypeIdentifierRestingHeartRate", 20 | "HKQuantityTypeIdentifierWalkingHeartRateAverage", 21 | "HKQuantityTypeIdentifierStepCount", 22 | ]: 23 | health_data.append({ 24 | "timestamp": record.attrib['startDate'], 25 | "type": record.attrib['type'], 26 | "value": float(record.attrib['value']), 27 | }) 28 | 29 | health_df = pd.DataFrame(health_data) 30 | health_df['timestamp'] = pd.to_datetime(health_df['timestamp']) 31 | return health_df 32 | 33 | 34 | # Function to parse screentime CSV 35 | def parse_screentime_csv(csv_file): 36 | screentime_df = pd.read_csv(csv_file) 37 | screentime_df['start_time'] = pd.to_datetime(screentime_df['start_time']) 38 | screentime_df['end_time'] = pd.to_datetime(screentime_df['end_time']) 39 | screentime_df['duration'] = (screentime_df['end_time'] - screentime_df['start_time']).dt.total_seconds() / 60 40 | return screentime_df 41 | 42 | 43 | # Analyze 24-hour periods before and after app usage 44 | def analyze_app_impact(app_name, health_df, screentime_df): 45 | # Filter for the selected app usage 46 | app_usage = screentime_df[screentime_df['app'].str.contains(app_name, case=False, na=False)] 47 | 48 | # Get timestamps of app usage 49 | app_hours = app_usage['start_time'].dt.floor('H').unique() 50 | 51 | # Identify 24-hour periods before and after app usage 52 | before_app_periods = [] 53 | after_app_periods = [] 54 | for hour in app_hours: 55 | before_app_periods.extend(pd.date_range(start=hour - pd.Timedelta(hours=24), periods=24, freq='h')) 56 | after_app_periods.extend(pd.date_range(start=hour, periods=24, freq='h')) 57 | 58 | before_app_periods = pd.Series(before_app_periods) 59 | after_app_periods = pd.Series(after_app_periods) 60 | 61 | # Ensure timestamps are timezone-naive 62 | health_df['timestamp'] = health_df['timestamp'].dt.tz_localize(None) 63 | 64 | # Approximate match health data with periods 65 | health_before = pd.merge_asof( 66 | pd.DataFrame({'timestamp': before_app_periods}).sort_values(by='timestamp'), 67 | health_df.sort_values(by='timestamp'), 68 | on='timestamp', 69 | direction='nearest' 70 | ) 71 | 72 | health_after = pd.merge_asof( 73 | pd.DataFrame({'timestamp': after_app_periods}).sort_values(by='timestamp'), 74 | health_df.sort_values(by='timestamp'), 75 | on='timestamp', 76 | direction='nearest' 77 | ) 78 | 79 | # Aggregate health data for comparison 80 | health_agg_before = health_before.groupby('type').agg({'value': ['mean', 'std']}).reset_index() 81 | health_agg_after = health_after.groupby('type').agg({'value': ['mean', 'std']}).reset_index() 82 | 83 | # Aggregate screentime data for before and after periods 84 | screentime_before = screentime_df[screentime_df['start_time'].dt.floor('H').isin(before_app_periods)] 85 | screentime_after = screentime_df[screentime_df['start_time'].dt.floor('H').isin(after_app_periods)] 86 | 87 | screentime_agg_before = screentime_before.groupby('app').agg({'duration': 'sum'}).reset_index() 88 | screentime_agg_after = screentime_after.groupby('app').agg({'duration': 'sum'}).reset_index() 89 | 90 | return health_agg_before, health_agg_after, screentime_agg_before, screentime_agg_after 91 | 92 | 93 | def main(): 94 | st.title("Brain Rot App Usage & Health Metrics Analysis") 95 | st.markdown(""" 96 | This app analyzes correlations between selected app usage (e.g., Reddit, TikTok) and health metrics such as 97 | heart rate, HRV, resting HR, walking HR, and step count. 98 | """) 99 | 100 | # File upload 101 | healthkit_file = "./data/export.xml" 102 | screentime_file = "./data/screentime_data_2024-12-19 20:27:29.388001.csv" 103 | 104 | if healthkit_file and screentime_file: 105 | # Parse data 106 | st.write("Parsing files...") 107 | health_df = parse_healthkit_export(healthkit_file) 108 | screentime_df = parse_screentime_csv(screentime_file) 109 | 110 | # App picker 111 | st.write("Select an app to analyze:") 112 | app_name = st.selectbox("Pick an app from the screentime data:", screentime_df['app'].unique()) 113 | 114 | if app_name: 115 | # Analyze app impact 116 | st.write(f"Analyzing data for 24-hour periods before and after {app_name} usage...") 117 | health_agg_before, health_agg_after, screentime_agg_before, screentime_agg_after = analyze_app_impact( 118 | app_name, health_df, screentime_df 119 | ) 120 | 121 | # Display aggregated health data 122 | st.write("Health Metrics (24 Hours Before App Usage):") 123 | st.dataframe(health_agg_before) 124 | 125 | st.write("Health Metrics (24 Hours After App Usage):") 126 | st.dataframe(health_agg_after) 127 | 128 | # Visualization: Health Metrics Comparison 129 | st.write("Health Metrics Comparison (Before vs. After App Usage)") 130 | health_agg_before.columns = ['_'.join(col).strip() if isinstance(col, tuple) else col for col in health_agg_before.columns] 131 | health_agg_after.columns = ['_'.join(col).strip() if isinstance(col, tuple) else col for col in health_agg_after.columns] 132 | 133 | fig = px.bar( 134 | pd.concat([ 135 | health_agg_before.assign(period='Before'), 136 | health_agg_after.assign(period='After') 137 | ]), 138 | x='type_', 139 | y='value_mean', 140 | color='period', 141 | barmode='group', 142 | error_y='value_std', 143 | labels={'type_': 'Health Metric', 'value_mean': 'Average Value'}, 144 | title="Health Metrics Comparison (Mean and Std Dev)" 145 | ) 146 | st.plotly_chart(fig) 147 | 148 | # Display aggregated screentime data 149 | st.write("Screentime (24 Hours Before App Usage):") 150 | st.dataframe(screentime_agg_before) 151 | 152 | st.write("Screentime (24 Hours After App Usage):") 153 | st.dataframe(screentime_agg_after) 154 | 155 | # Visualization: Screentime Comparison 156 | st.write("Screentime Comparison (Before vs. After App Usage)") 157 | fig = px.bar( 158 | pd.concat([ 159 | screentime_agg_before.assign(period='Before'), 160 | screentime_agg_after.assign(period='After') 161 | ]), 162 | x='app', 163 | y='duration', 164 | color='period', 165 | barmode='group', 166 | labels={'app': 'App', 'duration': 'Usage Duration'}, 167 | title="Screentime Comparison (Before vs. After App Usage)" 168 | ) 169 | st.plotly_chart(fig) 170 | 171 | 172 | if __name__ == "__main__": 173 | main() -------------------------------------------------------------------------------- /roi.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | import datetime 5 | from categories import categorize_apps 6 | 7 | def main(): 8 | st.title("Weekly ROI Analysis with Screentime & Browser History") 9 | 10 | # File uploaders 11 | screentime_file = st.file_uploader("Upload Screentime Data CSV", type=["csv"]) 12 | download_file = st.file_uploader("Upload Download Data CSV", type=["csv"]) 13 | browser_file = st.file_uploader("Upload Browser History CSV", type=["csv"]) 14 | 15 | # Start date input 16 | start_date = st.date_input( 17 | "Select Start Date", 18 | value=datetime.date(2024, 11, 1), 19 | min_value=datetime.date(2000, 1, 1), 20 | ) 21 | 22 | # -------------------------------------------------------------------- 23 | # 1) LOAD DATA 24 | # -------------------------------------------------------------------- 25 | # Screentime 26 | screentime_data = None 27 | if screentime_file: 28 | try: 29 | screentime_data = pd.read_csv(screentime_file) 30 | screentime_data['date'] = pd.to_datetime(screentime_data['start_time']) 31 | screentime_data['hours'] = pd.to_numeric(screentime_data['usage'], errors='coerce').fillna(0) 32 | # Filter based on start_date 33 | screentime_data = screentime_data[screentime_data['date'] >= pd.to_datetime(start_date)] 34 | screentime_data = categorize_apps(screentime_data) 35 | 36 | if st.checkbox("Show Screentime Raw Data"): 37 | st.dataframe(screentime_data.head(20)) 38 | except Exception as e: 39 | st.error(f"Error loading screentime data: {e}") 40 | screentime_data = None 41 | 42 | # Download Data 43 | download_values = [] 44 | if download_file: 45 | try: 46 | download_data_df = pd.read_csv(download_file) 47 | # Assume row 0 has the weekly downloads in columns[1:] 48 | download_values = download_data_df.iloc[0, 1:].tolist() 49 | if st.checkbox("Show Download Raw Data"): 50 | st.dataframe(download_data_df.head()) 51 | except Exception as e: 52 | st.error(f"Error loading download data: {e}") 53 | download_values = [] 54 | 55 | # Browser Data 56 | browser_df = None 57 | if browser_file: 58 | try: 59 | browser_df = pd.read_csv(browser_file) 60 | browser_df['Timestamp'] = pd.to_datetime(browser_df['Timestamp']) 61 | browser_df['date'] = browser_df['Timestamp'].dt.date 62 | 63 | if st.checkbox("Show Browser History Raw Data"): 64 | st.dataframe(browser_df.head(20)) 65 | except Exception as e: 66 | st.error(f"Error loading browser data: {e}") 67 | browser_df = None 68 | 69 | # Need screentime + downloads to calculate ROI 70 | if not (screentime_data is not None and len(download_values) > 0): 71 | st.warning("Please upload both Screentime and Download data to see ROI analysis.") 72 | return 73 | 74 | # -------------------------------------------------------------------- 75 | # 2) WEEKLY SCREENTIME & ROI 76 | # -------------------------------------------------------------------- 77 | # Group screentime by category/week 78 | weekly_data = ( 79 | screentime_data 80 | .groupby(['category', pd.Grouper(key='date', freq='W-MON')])['hours'] 81 | .sum() 82 | .unstack(fill_value=0) 83 | ) 84 | # Keep only columns/weeks >= start_date 85 | valid_cols = [c for c in weekly_data.columns if c >= pd.to_datetime(start_date)] 86 | weekly_data = weekly_data[valid_cols] 87 | # Transpose so that rows=weeks, columns=categories 88 | weekly_data = weekly_data.T.sort_index() 89 | 90 | # If we have 3 rows (weeks) but 40 download points, slice downloads to length 3 91 | if len(weekly_data.index) < len(download_values): 92 | st.warning( 93 | f"We have {len(weekly_data.index)} weeks of screentime but " 94 | f"{len(download_values)} download data points. " 95 | "Only partial ROI can be calculated. Slicing downloads to match screentime weeks." 96 | ) 97 | download_values = download_values[:len(weekly_data.index)] 98 | elif len(weekly_data.index) > len(download_values): 99 | # Rare case if more screentime weeks than downloads 100 | weekly_data = weekly_data.iloc[:len(download_values)] 101 | 102 | # Add a 'downloads' column 103 | weekly_data['downloads'] = download_values 104 | weekly_data['downloads'] = weekly_data['downloads'].replace(0, 1e-9) # avoid div-by-zero 105 | 106 | # Screentime ROI (hours per download) 107 | categories = [c for c in weekly_data.columns if c != 'downloads'] 108 | roi_data = pd.DataFrame(index=weekly_data.index) 109 | for cat in categories: 110 | roi_data[cat] = weekly_data[cat] / weekly_data['downloads'] 111 | 112 | # 3) WEEKLY BROWSER HISTORY AGGREGATION 113 | browser_roi = pd.DataFrame() 114 | if browser_df is not None: 115 | # Group browser data by category and weekly 116 | weekly_browsing = ( 117 | browser_df 118 | .groupby(['Category', pd.Grouper(key='Timestamp', freq='W-MON')]) 119 | .size() 120 | .unstack(fill_value=0) 121 | ) 122 | 123 | # Transpose so rows=weeks, columns=categories 124 | weekly_browsing = weekly_browsing.T.sort_index() # weekly_browsing.index = weekly Mondays 125 | 126 | st.write("Raw weekly_browsing (before alignment):", weekly_browsing) 127 | 128 | # 1) INTERSECT the weekly_browsing.index with roi_data.index (the screentime weeks) 129 | common_weeks = weekly_browsing.index.intersection(weekly_data.index) 130 | weekly_browsing = weekly_browsing.loc[common_weeks] 131 | weekly_data_common = weekly_data.loc[common_weeks] 132 | 133 | weekly_browsing['downloads'] = weekly_data_common['downloads'].replace(0, 1e-9) 134 | 135 | # 4) Finally, compute visits/download = "browser ROI" 136 | browser_roi = pd.DataFrame(index=common_weeks) 137 | for cat in weekly_browsing.columns.drop('downloads'): 138 | browser_roi[cat] = weekly_browsing[cat] / weekly_browsing['downloads'] 139 | 140 | st.write("browser_roi (aligned with screentime weeks):", browser_roi) 141 | # -------------------------------------------------------------------- 142 | # 4) BAR CHART: SCREENTIME ROI + BROWSER ROI 143 | # We’ll plot two bars for each category: 144 | # 1) Screentime hours/download 145 | # 2) Browser visits/download 146 | # -------------------------------------------------------------------- 147 | st.subheader("Weekly ROI: Time Spent & Browser Visits per Download by Category") 148 | 149 | # Let user select categories from screentime or browser 150 | all_possible_cats = sorted(set(roi_data.columns) | set(browser_roi.columns)) 151 | selected_categories = st.multiselect( 152 | "Select Categories to Plot", 153 | options=all_possible_cats, 154 | default=["Development", "Marketing"] 155 | ) 156 | 157 | if not selected_categories: 158 | st.warning("Please select at least one category to plot.") 159 | return 160 | 161 | fig, ax = plt.subplots(figsize=(12, 8)) 162 | bar_width = 2 163 | for i, cat in enumerate(selected_categories): 164 | # x-values shift so bars don’t overlap 165 | x_positions = roi_data.index + pd.Timedelta(days=i*3) 166 | 167 | # Screentime ROI bar 168 | if cat in roi_data.columns: 169 | ax.bar( 170 | x_positions, 171 | roi_data[cat], 172 | width=bar_width, 173 | label=f"{cat} (Screentime)", 174 | alpha=0.7 175 | ) 176 | 177 | # Browser ROI bar 178 | if cat in browser_roi.columns: 179 | # shift by 1 day to show side-by-side 180 | ax.bar( 181 | x_positions + pd.Timedelta(days=1), 182 | browser_roi[cat], 183 | width=bar_width, 184 | label=f"{cat} (Browser)", 185 | alpha=0.7 186 | ) 187 | 188 | ax.set_title("Weekly ROI: Screentime Hours per Download", fontsize=16) 189 | ax.set_xlabel("Week", fontsize=12) 190 | ax.set_ylabel("ROI (Hours/Download)", fontsize=12) 191 | ax.legend() 192 | ax.grid(axis='y') 193 | plt.xticks(rotation=45, ha='right') 194 | plt.tight_layout() 195 | st.pyplot(fig) 196 | 197 | # -------------------------------------------------------------------- 198 | # 4B) SEPARATE CHART FOR BROWSER ROI ONLY 199 | # -------------------------------------------------------------------- 200 | st.subheader("Browser ROI: Visits per Download by Category") 201 | 202 | # Gather browser-only categories (columns in browser_roi) 203 | browser_categories = sorted(browser_roi.columns) 204 | 205 | # Prompt user to select which browser categories to visualize 206 | selected_browser_cats = st.multiselect( 207 | "Select Browser Categories to Plot", 208 | options=browser_categories, 209 | default=browser_categories[:3] # Pick the first few as a default 210 | ) 211 | 212 | if not selected_browser_cats: 213 | st.warning("Please select at least one browser category to plot.") 214 | else: 215 | # Build a bar chart similar to your screentime chart 216 | fig_browser, ax_browser = plt.subplots(figsize=(12, 8)) 217 | bar_width = 2 218 | for i, cat in enumerate(selected_browser_cats): 219 | x_positions = browser_roi.index + pd.Timedelta(days=i * 3) 220 | ax_browser.bar( 221 | x_positions, 222 | browser_roi[cat], 223 | width=bar_width, 224 | label=cat, 225 | alpha=0.7 226 | ) 227 | 228 | ax_browser.set_title("Weekly Browser ROI: Visits per Download", fontsize=16) 229 | ax_browser.set_xlabel("Week Start (Mon)", fontsize=12) 230 | ax_browser.set_ylabel("Visits per Download", fontsize=12) 231 | ax_browser.legend() 232 | ax_browser.grid(axis='y') 233 | plt.xticks(rotation=45, ha='right') 234 | plt.tight_layout() 235 | st.pyplot(fig_browser) 236 | # -------------------------------------------------------------------- 237 | # 5) DOWNLOAD PROCESSED DATA 238 | # -------------------------------------------------------------------- 239 | @st.cache_data 240 | def convert_df_to_csv(df): 241 | return df.to_csv(index=True).encode('utf-8') 242 | 243 | # Optionally let the user download screentime ROI data 244 | st.download_button( 245 | label="Download Screentime ROI Data as CSV", 246 | data=convert_df_to_csv(roi_data), 247 | file_name="screentime_ROI.csv", 248 | mime="text/csv", 249 | ) 250 | # And optionally the browser ROI data 251 | if browser_df is not None: 252 | st.download_button( 253 | label="Download Browser ROI Data as CSV", 254 | data=convert_df_to_csv(browser_roi), 255 | file_name="browser_ROI.csv", 256 | mime="text/csv", 257 | ) 258 | 259 | if __name__ == "__main__": 260 | main() -------------------------------------------------------------------------------- /categories.py: -------------------------------------------------------------------------------- 1 | def categorize_apps(df): 2 | """ 3 | Categorize apps into detailed productivity levels given a screentime dataframe. 4 | """ 5 | # Define app categories with finer granularity 6 | app_categories = { 7 | # Development Tools 8 | 'com.microsoft.VSCode': 'Development', 9 | 'com.apple.dt.Xcode': 'Development', 10 | 'com.figma.Desktop': 'Development', 11 | 'com.github.GitHubDesktop': 'Development', 12 | 'cc.arduino.IDE2': 'Development', 13 | 'app.codeedit.CodeEdit': 'Development', 14 | 'com.roadesign.Codyeapp': 'Development', 15 | 'org.openmv.openmvide': 'Development', 16 | 'com.oracle.workbench.MySQLWorkbench': 'Development', 17 | 'com.apple.dt.AutomationModeUI': 'Development', 18 | 'com.apple.Terminal': 'Development', 19 | 'com.apple.dt.CommandLineTools.installondemand': 'Development', 20 | 21 | # Marketing 22 | 'com.apple.FinalCutTrial': 'Marketing', 23 | 'com.Ai.NeuroNote': 'Development', # As per your instruction 24 | 'com.Ai.NeuroNote3': 'Development', # As per your instruction 25 | 'com.hnc.Discord': 'Social Media', # Special case 26 | 'company.thebrowser.Browser': 'Development', # As per your instruction 27 | 'com.hammerandchisel.discord': 'Social Media', 28 | 'com.github.GitHubClient': 'Development', 29 | 'com.framer.electron': 'Development', # Assuming creative/development hybrid 30 | 'com.nvidia.gfnpc.mall': 'Entertainment', # Gaming/Entertainment 31 | 'com.apple.VoiceOver': 'Utility', # System Utility 32 | 33 | # Social Media 34 | 'com.hnc.Discord': 'Social Media', # Special case 35 | 'com.apple.FaceTime': 'Social Media', 36 | 'com.apple.findmy': 'Utility', 37 | 'com.apple.VoiceOver': 'Utility', 38 | 'com.apple.Spotlight': 'Utility', 39 | 40 | # Creative 41 | 'com.canva.CanvaDesktop': 'Development', # As per your instruction 42 | 'com.framer.electron': 'Development', # As per your instruction 43 | 'com.apple.FontBook': 'Creative', 44 | 'com.apple.Photos': 'Creative', 45 | 'com.apple.GenerativePlaygroundApp': 'Creative', 46 | 'com.apple.FolderActionsSetup': 'Creative', 47 | 48 | # Entertainment 49 | 'com.netflix.Netflix': 'Entertainment', 50 | 'com.apple.Music': 'Entertainment', 51 | 'com.apple.PhotoBooth': 'Entertainment', 52 | 'com.apple.Chess': 'Entertainment', 53 | 'com.apple.TV': 'Entertainment', 54 | 55 | # Browsing 56 | 'com.google.Chrome': 'Browsing', 57 | 'com.apple.Safari': 'Browsing', 58 | 59 | # AI Productivity 60 | 'com.openai.chat': 'Development', # As per your instruction 61 | 'com.Ai.NeuroNote': 'Development', 62 | 'com.Ai.NeuroNote3': 'Development', 63 | 64 | # Utility 65 | 'com.apple.calculator': 'Utility', 66 | 'com.apple.Dictionary': 'Utility', 67 | 'com.apple.DiskUtility': 'Utility', 68 | 'com.apple.finder': 'Utility', 69 | 'com.apple.Maps': 'Utility', 70 | 'com.apple.SystemPreferences': 'Utility', 71 | 'com.apple.Terminal': 'Development', # Also fits Utility 72 | 'com.apple.VoiceMemos': 'Utility', 73 | 'com.apple.reminders': 'Utility', 74 | 'com.apple.notes': 'Utility', 75 | 'com.apple.calendar': 'Utility', 76 | 'com.apple.ActivityMonitor': 'Utility', 77 | 'com.apple.Console': 'Utility', 78 | 'com.apple.DigitalColorMeter': 'Utility', 79 | 'com.apple.DiskUtility': 'Utility', 80 | 'com.apple.FontBook': 'Creative', 81 | 'com.apple.GenerativePlaygroundApp': 'Creative', 82 | 'com.apple.launchpad.launcher': 'Utility', 83 | 'com.apple.mail': 'Utility', 84 | 'com.apple.Passwords': 'Utility', 85 | 'com.apple.Preview': 'Utility', 86 | 'com.apple.TextEdit': 'Utility', 87 | 'com.apple.QuickTimePlayerX': 'Entertainment', 88 | 'com.apple.screencaptureui': 'Utility', 89 | 'com.apple.scriptEditor': 'Utility', 90 | # ... Add additional Utility apps as needed 91 | 92 | # System 93 | 'com.apple.MRT': 'System', 94 | 'com.apple.XProtectFramework.XProtect': 'System', 95 | 'com.apple.SyncServices.AppleMobileDeviceHelper': 'System', 96 | 'com.apple.SyncServices.AppleMobileSync': 'System', 97 | 'com.apple.MobileDeviceUpdater': 'System', 98 | 'com.apple.ScriptEditor.id.cocoa-applet-template': 'System', 99 | 'com.apple.ScriptEditor.id.droplet-with-settable-properties-template': 'System', 100 | 'com.apple.ScriptEditor.id.file-processing-droplet-template': 'System', 101 | 'com.apple.ScriptEditor.id.image-file-processing-droplet-template': 'System', 102 | 'com.apple.python3': 'System', 103 | 'com.apple.print.AirScanLegacyDiscovery': 'System', 104 | 'com.apple.AppStore': 'Utility', # System App 105 | 'com.apple.Automator': 'Utility', # System App 106 | 'com.apple.iBooksX': 'Entertainment', # System App 107 | 'com.apple.calculator': 'Utility', # System App 108 | 'com.apple.iCal': 'Utility', # System App 109 | 'com.apple.Chess': 'Entertainment', # System App 110 | 'com.apple.clock': 'Utility', # System App 111 | 'com.apple.AddressBook': 'Utility', # System App 112 | 'com.apple.Dictionary': 'Utility', # System App 113 | 'com.apple.FaceTime': 'Social Media', # System App 114 | 'com.apple.findmy': 'Utility', # System App 115 | 'com.apple.FontBook': 'Creative', # System App 116 | 'com.apple.freeform': 'Creative', # System App 117 | 'com.apple.Home': 'Utility', # System App 118 | 'com.apple.Image_Capture': 'Utility', # System App 119 | 'com.apple.GenerativePlaygroundApp': 'Creative', # System App 120 | 'com.apple.launchpad.launcher': 'Utility', # System App 121 | 'com.apple.mail': 'Utility', # System App 122 | 'com.apple.Maps': 'Utility', # System App 123 | 'com.apple.MobileSMS': 'Utility', # System App 124 | 'com.apple.exposelauncher': 'Utility', # System App 125 | 'com.apple.Music': 'Entertainment', # System App 126 | 'com.apple.news': 'Entertainment', # System App 127 | 'com.apple.Notes': 'Utility', # System App 128 | 'com.apple.Passwords': 'Utility', # System App 129 | 'com.apple.PhotoBooth': 'Entertainment', # System App 130 | 'com.apple.Photos': 'Creative', # System App 131 | 'com.apple.podcasts': 'Entertainment', # System App 132 | 'com.apple.Preview': 'Utility', # System App 133 | 'com.apple.QuickTimePlayerX': 'Entertainment', # System App 134 | 'com.apple.reminders': 'Utility', # System App 135 | 'com.apple.shortcuts': 'Productive', # System App 136 | 'com.apple.siri.launcher': 'Utility', # System App 137 | 'com.apple.Stickies': 'Utility', # System App 138 | 'com.apple.stocks': 'Entertainment', # System App 139 | 'com.apple.systempreferences': 'Utility', # System App 140 | 'com.apple.TV': 'Entertainment', # System App 141 | 'com.apple.TextEdit': 'Utility', # System App 142 | 'com.apple.backup.launcher': 'Utility', # System App 143 | 'com.apple.helpviewer': 'Utility', # System App 144 | 'com.apple.ActivityMonitor': 'Utility', # System App 145 | 'com.apple.airport.airportutility': 'Utility', # System App 146 | 'com.apple.audio.AudioMIDISetup': 'Utility', # System App 147 | 'com.apple.BluetoothFileExchange': 'Utility', # System App 148 | 'com.apple.bootcampassistant': 'Utility', # System App 149 | 'com.apple.ColorSyncUtility': 'Utility', # System App 150 | 'com.apple.Console': 'Utility', # System App 151 | 'com.apple.DigitalColorMeter': 'Utility', # System App 152 | 'com.apple.DiskUtility': 'Utility', # System App 153 | 'com.apple.grapher': 'Utility', # System App 154 | 'com.apple.MigrateAssistant': 'Utility', # System App 155 | 'com.apple.printcenter': 'Utility', # System App 156 | 'com.apple.ScreenSharing': 'Utility', # System App 157 | 'com.apple.screenshot.launcher': 'Utility', # System App 158 | 'com.apple.ScriptEditor2': 'Utility', # System App 159 | 'com.apple.SystemProfiler': 'Utility', # System App 160 | 'com.apple.Terminal': 'Development', # System App 161 | 'com.apple.VoiceOverUtility': 'Utility', # System App 162 | 'com.apple.VoiceMemos': 'Utility', # System App 163 | 'com.apple.weather': 'Utility', # System App 164 | 'com.apple.ScreenContinuity': 'Utility', # System App 165 | 166 | # Other Applications 167 | 'com.if.Amphetamine': 'Marketing', # As per your instruction 168 | 'com.apple.configurator.ui': 'Utility', # Apple Configurator 169 | 'company.thebrowser.Browser': 'Browsing', # As per your instruction 170 | 'com.goodsnooze.bakery': 'Marketing', # Assumed based on name 171 | 'com.nonstrict.Bezel-appstore': 'Unknown', # Limited info 172 | 'org.blenderfoundation.blender': 'Development', # Blender is 3D modeling 173 | 'com.canva.CanvaDesktop': 'Development', # As per your instruction 174 | 'com.openai.chat': 'Development', # As per your instruction 175 | 'com.macpaw.CleanMyMac-mas': 'Utility', 176 | 'app.codeedit.CodeEdit': 'Development', 177 | 'com.roadesign.Codyeapp': 'Development', 178 | 'de.ixeau.Curve': 'Development', # Assumed 179 | 'com.lukilabs.lukiapp': 'Marketing', # As per your instruction 180 | 'app.diagrams.DiagramsMac.mas': 'Development', 181 | 'com.hnc.Discord': 'Social Media', 182 | 'com.docker.docker': 'Development', 183 | 'com.figma.Desktop': 'Development', # As per your instruction 184 | 'com.apple.FinalCutTrial': 'Marketing', # As per your instruction 185 | 'com.framer.electron': 'Development', # As per your instruction 186 | 'com.nvidia.gfnpc.mall': 'Entertainment', 187 | 'com.github.GitHubClient': 'Development', 188 | 'com.google.Chrome': 'Browsing', 189 | 'com.jomo.Jomo': 'Unknown', # Limited info 190 | 'ai.elementlabs.lmstudio': 'Development', # Assumed 191 | 'com.microsoft.teams': 'School', 192 | 'com.microsoft.Word': 'School', 193 | 'com.oracle.workbench.MySQLWorkbench': 'Development', 194 | 'com.Ai.NeuroNote': 'Development', 195 | 'notion.id': 'Productive', # Notion is productivity 196 | 'com.apple.iWork.Numbers': 'Development', 197 | 'com.electron.ollama': 'Development', 198 | 'org.openmv.openmvide': 'Development', 199 | 'com.postmanlabs.mac': 'Development', 200 | 'org.prismlauncher.PrismLauncher': 'Development', 201 | 'org.raspberrypi.imagingutility': 'Utility', 202 | 'com.apple.RealityConverter': 'Development', # Reality Converter is AR-related 203 | 'com.swiftLee.RocketSim': 'Development', 204 | 'com.mortenjust.Rendermock': 'Development', 205 | 'com.apple.SFSymbols-beta': 'Development', 206 | 'com.apple.Safari': 'Browsing', 207 | 'com.timpler.screenstudio': 'Marketing', 208 | 'com.tinyspeck.slackmacgap': 'Social Media', 209 | 'com.sebvidal.Snippet': 'Development', 210 | 'dev.erikschnell.CodeSnippets': 'Development', 211 | 'com.termius.mac': 'Development', 212 | 'com.apple.TestFlight': 'Development', # TestFlight is for testing apps 213 | 'com.install4j.1106-5897-7327-6550.5': 'School', # Visual Paradigm is UML tool 214 | 'com.microsoft.VSCode': 'Development', 215 | 'com.apple.dt.Xcode': 'Development', 216 | 'io.balena.etcher': 'Development', 217 | 'com.mlobodzinski.Stoic': 'Development', 218 | 'us.zoom.xos': 'Marketing', 219 | 'com.apple.MRT': 'System', 220 | 'com.apple.XProtectFramework.XProtect': 'System', 221 | 'com.apple.SyncServices.AppleMobileDeviceHelper': 'System', 222 | 'com.apple.SyncServices.AppleMobileSync': 'System', 223 | 'com.apple.MobileDeviceUpdater': 'System', 224 | 'com.apple.ScriptEditor.id.cocoa-applet-template': 'System', 225 | 'com.apple.ScriptEditor.id.droplet-with-settable-properties-template': 'System', 226 | 'com.apple.ScriptEditor.id.file-processing-droplet-template': 'System', 227 | 'com.apple.ScriptEditor.id.image-file-processing-droplet-template': 'System', 228 | 'com.apple.python3': 'System', 229 | 'com.apple.print.AirScanLegacyDiscovery': 'System', 230 | 'com.apple.AppStore': 'Utility', 231 | 'com.apple.Automator': 'Utility', 232 | 'com.apple.iBooksX': 'Entertainment', 233 | 'com.apple.calculator': 'Utility', 234 | 'com.apple.iCal': 'Utility', 235 | 'com.apple.Chess': 'Entertainment', 236 | 'com.apple.clock': 'Utility', 237 | 'com.apple.AddressBook': 'Utility', 238 | 'com.apple.Dictionary': 'Utility', 239 | 'com.apple.FaceTime': 'Social Media', 240 | 'com.apple.findmy': 'Utility', 241 | 'com.apple.FontBook': 'Creative', 242 | 'com.apple.freeform': 'Creative', 243 | 'com.apple.Home': 'Utility', 244 | 'com.apple.Image_Capture': 'Utility', 245 | 'com.apple.GenerativePlaygroundApp': 'Creative', 246 | 'com.apple.launchpad.launcher': 'Utility', 247 | 'com.apple.mail': 'Utility', 248 | 'com.apple.Maps': 'Utility', 249 | 'com.apple.MobileSMS': 'Utility', 250 | 'com.apple.exposelauncher': 'Utility', 251 | 'com.apple.Music': 'Entertainment', 252 | 'com.apple.news': 'Entertainment', 253 | 'com.apple.Notes': 'Utility', 254 | 'com.apple.Passwords': 'Utility', 255 | 'com.apple.PhotoBooth': 'Entertainment', 256 | 'com.apple.Photos': 'Creative', 257 | 'com.apple.podcasts': 'Entertainment', 258 | 'com.apple.Preview': 'Utility', 259 | 'com.apple.QuickTimePlayerX': 'Entertainment', 260 | 'com.apple.reminders': 'Utility', 261 | 'com.apple.shortcuts': 'Productive', 262 | 'com.apple.siri.launcher': 'Utility', 263 | 'com.apple.Stickies': 'Utility', 264 | 'com.apple.stocks': 'Entertainment', 265 | 'com.apple.systempreferences': 'Utility', 266 | 'com.apple.TV': 'Entertainment', 267 | 'com.apple.TextEdit': 'Utility', 268 | 'com.apple.backup.launcher': 'Utility', 269 | 'com.apple.helpviewer': 'Utility', 270 | 'com.apple.ActivityMonitor': 'Utility', 271 | 'com.apple.airport.airportutility': 'Utility', 272 | 'com.apple.audio.AudioMIDISetup': 'Utility', 273 | 'com.apple.BluetoothFileExchange': 'Utility', 274 | 'com.apple.bootcampassistant': 'Utility', 275 | 'com.apple.ColorSyncUtility': 'Utility', 276 | 'com.apple.Console': 'Utility', 277 | 'com.apple.DigitalColorMeter': 'Utility', 278 | 'com.apple.DiskUtility': 'Utility', 279 | 'com.apple.grapher': 'Utility', 280 | 'com.apple.MigrateAssistant': 'Utility', 281 | 'com.apple.printcenter': 'Utility', 282 | 'com.apple.ScreenSharing': 'Utility', 283 | 'com.apple.screenshot.launcher': 'Utility', 284 | 'com.apple.ScriptEditor2': 'Utility', 285 | 'com.apple.SystemProfiler': 'Utility', 286 | 'com.apple.Terminal': 'Development', 287 | 'com.apple.VoiceOverUtility': 'Utility', 288 | 'com.apple.VoiceMemos': 'Utility', 289 | 'com.apple.weather': 'Utility', 290 | 'com.apple.ScreenContinuity': 'Utility', 291 | 292 | # Helper and System Services (categorized as 'System') 293 | 'com.apple.ClassroomStudentMenuExtra': 'System', 294 | 'com.apple.ColorSyncCalibrator': 'System', 295 | 'com.apple.AOSUIPrefPaneLauncher': 'System', 296 | 'com.apple.AVB-Audio-Configuration': 'System', 297 | 'com.apple.print.add': 'System', 298 | 'com.apple.AddressBook.UrlForwarder': 'System', 299 | 'com.apple.AirPlayUIAgent': 'System', 300 | 'com.apple.AirPortBaseStationAgent': 'System', 301 | 'com.apple.AppleScriptUtility': 'System', 302 | 'com.apple.AboutThisMacLauncher': 'System', 303 | 'com.apple.archiveutility': 'Utility', 304 | 'com.apple.DVDPlayer': 'Entertainment', 305 | 'com.apple.DeskCam': 'Utility', 306 | 'com.apple.DirectoryUtility': 'Utility', 307 | 'com.apple.ExpansionSlotUtility': 'Utility', 308 | 'com.apple.appleseed.FeedbackAssistant': 'Utility', 309 | 'com.apple.FolderActionsSetup': 'Creative', 310 | 'com.apple.keychainaccess': 'Utility', 311 | 'com.apple.Ticket-Viewer': 'Utility', 312 | 'com.apple.wifi.diagnostics': 'Utility', 313 | 'com.apple.IPAInstaller': 'Utility', 314 | 'com.apple.AskToMessagesHost': 'Utility', 315 | 'com.apple.Automator.Automator-Application-Stub': 'Utility', 316 | 'com.apple.AutomatorInstaller': 'Utility', 317 | 'com.apple.Batteries': 'Utility', 318 | 'com.apple.BluetoothSetupAssistant': 'Utility', 319 | 'com.apple.BluetoothUIServer': 'Utility', 320 | 'com.apple.BluetoothUIService': 'Utility', 321 | 'com.apple.CalendarFileHandler': 'Utility', 322 | 'com.apple.CaptiveNetworkAssistant': 'Utility', 323 | 'com.apple.CertificateAssistant': 'Utility', 324 | 'com.apple.controlcenter': 'Utility', 325 | 'com.apple.controlstrip': 'Utility', 326 | 'com.apple.CoreLocationAgent': 'Utility', 327 | 'com.apple.coreservices.uiagent': 'Utility', 328 | 'com.apple.NewDeviceOutreachApp': 'Utility', 329 | 'com.apple.databaseevents': 'Utility', 330 | 'com.apple.DiagnosticsReporter': 'Utility', 331 | 'com.apple.DiscHelper': 'Utility', 332 | 'com.apple.DiskImageMounter': 'Utility', 333 | 'com.apple.dock': 'Utility', 334 | 'com.apple.DwellControl': 'Utility', 335 | 'com.apple.EnhancedLogging': 'Utility', 336 | 'com.apple.EraseAssistant': 'Utility', 337 | 'com.apple.EscrowSecurityAlert': 'Utility', 338 | 'com.apple.Family': 'Utility', 339 | 'com.apple.FileProvider-Feedback': 'Utility', 340 | 'com.apple.Finder': 'Utility', 341 | 'com.apple.FolderActionsDispatcher': 'Utility', 342 | 'com.apple.gamecenter': 'Entertainment', 343 | 'com.apple.IOUIAgent': 'Utility', 344 | 'com.apple.imageevents': 'Utility', 345 | 'com.apple.dt.CommandLineTools.installondemand': 'Development', 346 | 'com.apple.PackageUIKit.Install-in-Progress': 'Utility', 347 | 'com.apple.Installer-Progress': 'Utility', 348 | 'com.apple.installer': 'Utility', 349 | 'com.apple.JavaLauncher': 'Development', 350 | 'com.apple.KeyboardAccessAgent': 'Utility', 351 | 'com.apple.KeyboardSetupAssistant': 'Utility', 352 | 'com.apple.security.Keychain-Circle-Notification': 'Utility', 353 | 'com.apple.Language-Chooser': 'Utility', 354 | 'com.apple.MTLReplayer': 'Utility', 355 | 'com.apple.ManagedClient': 'Utility', 356 | 'com.apple.MediaMLPluginApp': 'Utility', 357 | 'com.apple.MemorySlotUtility': 'Utility', 358 | 'com.apple.musicrecognition.mac': 'Entertainment', 359 | 'com.apple.NetAuthAgent': 'Utility', 360 | 'com.apple.notificationcenterui': 'Utility', 361 | 'com.apple.NowPlayingTouchUI': 'Utility', 362 | 'com.apple.OBEXAgent': 'Utility', 363 | 'com.apple.ODSAgent': 'Utility', 364 | 'com.apple.OSDUIHelper': 'Utility', 365 | 'com.apple.PIPAgent': 'Utility', 366 | 'com.apple.PairedDevices': 'Utility', 367 | 'com.apple.Pass-Viewer': 'Utility', 368 | 'com.apple.PeopleMessageService': 'Utility', 369 | 'com.apple.PeopleViewService': 'Utility', 370 | 'com.apple.PowerChime': 'Utility', 371 | 'com.apple.PreviewShell': 'Utility', 372 | 'com.apple.displaycalibrator': 'Utility', 373 | 'com.apple.ProblemReporter': 'Utility', 374 | 'com.apple.mcx.ProfileHelper': 'Utility', 375 | 'com.apple.RapportUIAgent': 'Utility', 376 | 'com.apple.pluginIM.pluginIMRegistrator': 'Utility', 377 | 'com.apple.RemoteDesktopAgent': 'Utility', 378 | 'com.apple.RemoteDesktopMessageAgent': 'Utility', 379 | 'com.apple.SSMenuAgent': 'Utility', 380 | 'com.apple.OAHSoftwareUpdateApp': 'Utility', 381 | 'com.apple.ScreenTimeWidgetApplication': 'Utility', 382 | 'com.apple.ScreenSaver.Engine': 'Utility', 383 | 'com.apple.ScriptMenuApp': 'Utility', 384 | 'com.apple.ScriptMonitor': 'Utility', 385 | 'com.apple.SetupAssistant': 'Utility', 386 | 'com.apple.shortcuts.droplet': 'Utility', 387 | 'com.apple.shortcuts.events': 'Utility', 388 | 'com.apple.ShortcutsActions': 'Utility', 389 | 'com.apple.Siri': 'Utility', 390 | 'com.apple.SoftwareUpdate': 'Utility', 391 | 'com.apple.SpacesTouchBarAgent': 'Utility', 392 | 'com.apple.Spotlight': 'Utility', 393 | 'com.apple.windowmanager.StageManagerOnboarding': 'Utility', 394 | 'com.apple.systemevents': 'Utility', 395 | 'com.apple.systemuiserver': 'Utility', 396 | 'com.apple.TextInputMenuAgent': 'Utility', 397 | 'com.apple.TextInputSwitcher': 'Utility', 398 | 'com.apple.ThermalTrap': 'Utility', 399 | 'com.apple.timemachine.HelperAgent': 'Utility', 400 | 'com.apple.tips': 'Utility', 401 | 'com.apple.UIKitSystemApp': 'Utility', 402 | 'com.apple.UniversalAccessControl': 'Utility', 403 | 'com.apple.universalcontrol': 'Utility', 404 | 'com.apple.UnmountAssistantAgent': 'Utility', 405 | 'com.apple.UserNotificationCenter': 'Utility', 406 | 'com.apple.VoiceOver': 'Utility', 407 | 'com.apple.wallpaper.agent': 'Utility', 408 | 'com.apple.WatchFaceAlert': 'Utility', 409 | 'com.apple.wifi.WiFiAgent': 'Utility', 410 | 'com.apple.widgetkit.simulator': 'Utility', 411 | 'com.apple.WindowManager': 'Utility', 412 | 'com.apple.windowmanager.ShowDesktopEducation': 'Utility', 413 | 'com.apple.WorkoutAlert-Mac': 'Utility', 414 | 'com.apple.icq': 'Utility', 415 | 'com.apple.CloudKit.ShareBear': 'Utility', 416 | 'com.apple.loginwindow': 'System', 417 | 'com.apple.rcd': 'System', 418 | 'com.apple.screencaptureui': 'Utility', 419 | 'com.apple.AddressBook.sync': 'Utility', 420 | 'com.apple.ABAssistantService': 'Utility', 421 | 'com.apple.AddressBook.abd': 'Utility', 422 | 'com.apple.AddressBookSourceSync': 'Utility', 423 | 'com.apple.FontRegistryUIAgent': 'Utility', 424 | 'com.apple.speech.synthesis.SpeechSynthesisServer': 'Utility', 425 | 'com.apple.CMViewSrvc': 'Utility', 426 | 'com.apple.ContinuityCaptureOnboardingUI': 'Utility', 427 | 'com.apple.ctkbind': 'Utility', 428 | 'com.apple.quicklook.qlmanage': 'Utility', 429 | 'com.apple.QuickLookDaemon': 'Utility', 430 | 'com.apple.quicklook.QuickLookSimulator': 'Utility', 431 | 'com.apple.quicklook.ui.helper': 'Utility', 432 | 'com.apple.syncserver': 'Utility', 433 | 'com.tcltk.wish': 'Development', # Tcl/Tk interpreter 434 | 'com.apple.BuildWebPage': 'Development', 435 | 'com.apple.MakePDF': 'Development', 436 | 'com.apple.AirScanScanner': 'Utility', 437 | 'com.apple.50onPaletteIM': 'Utility', 438 | 'com.apple.inputmethod.Ainu': 'Utility', 439 | 'com.apple.inputmethod.AssistiveControl': 'Utility', 440 | 'com.apple.CharacterPaletteIM': 'Utility', 441 | 'com.apple.inputmethod.ironwood': 'Utility', 442 | 'com.apple.EmojiFunctionRowItem-Container': 'Utility', 443 | 'com.apple.JapaneseIM.KanaTyping': 'Utility', 444 | 'com.apple.JapaneseIM.RomajiTyping': 'Utility', 445 | 'com.apple.KIM-Container': 'Utility', 446 | 'com.apple.inputmethod.PluginIM': 'Utility', 447 | 'com.apple.PAH-Container': 'Utility', 448 | 'com.apple.SCIM-Container': 'Utility', 449 | 'com.apple.TCIM-Container': 'Utility', 450 | 'com.apple.TYIM-Container': 'Utility', 451 | 'com.apple.inputmethod.Tamil': 'Utility', 452 | 'com.apple.TrackpadIM-Container': 'Utility', 453 | 'com.apple.TransliterationIM-Container': 'Utility', 454 | 'com.apple.VIM-Container': 'Utility', 455 | 'com.apple.iCloudUserNotificationsd': 'Utility', 456 | 'com.apple.AOSAlertManager': 'Utility', 457 | 'com.apple.AOSHeartbeat': 'Utility', 458 | 'com.apple.AOSPushRelay': 'Utility', 459 | 'com.apple.accessibility.LiveTranscriptionAgent': 'Utility', 460 | 'com.apple.accessibility.LiveSpeech': 'Utility', 461 | 'com.apple.AccessibilityVisualsAgent': 'Utility', 462 | 'com.apple.Calibration-Assistant': 'Utility', 463 | 'com.apple.AppSSOAgent': 'Utility', 464 | 'com.apple.KerberosMenuExtra': 'Utility', 465 | 'com.apple.AMSEngagementViewService': 'Utility', 466 | 'com.apple.AskPermissionUI': 'Utility', 467 | 'com.apple.AutoFillPanelService': 'Utility', 468 | 'com.apple.dt.AutomationModeUI': 'Development', 469 | 'com.apple.backgroundtaskmanagement.agent': 'Utility', 470 | 'com.apple.bird': 'Utility', 471 | 'com.apple.storeuid': 'Utility', 472 | 'com.apple.CCE.CIMFindInputCode': 'Utility', 473 | 'com.apple.FollowUpUI': 'Utility', 474 | 'com.apple.frameworks.diskimages.diuiagent': 'Utility', 475 | 'com.apple.eap8021x.eaptlstrust': 'Utility', 476 | 'com.apple.familycontrols.useragent': 'Utility', 477 | 'com.apple.FeedbackRemoteView': 'Utility', 478 | 'com.apple.FindMyMacMessenger': 'Utility', 479 | 'com.apple.identityservicesd': 'Utility', 480 | 'com.apple.idsfoundation.IDSRemoteURLConnectionAgent': 'Utility', 481 | 'com.apple.imagent': 'Utility', 482 | 'com.apple.IMAutomaticHistoryDeletionAgent': 'Utility', 483 | 'com.apple.imtransferservices.IMTransferAgent': 'Utility', 484 | 'com.apple.nbagent': 'Utility', 485 | 'com.apple.LinkedNotesUIService': 'Utility', 486 | 'com.apple.privatecloudcomputed': 'Utility', 487 | 'com.apple.ScreenReaderUIServer': 'Utility', 488 | 'com.apple.VoiceOverQuickstart': 'Utility', 489 | 'com.apple.AquaAppearanceHelper': 'Utility', 490 | 'com.apple.sociallayerd': 'Utility', 491 | 'com.apple.SoftwareUpdateNotificationManager': 'Utility', 492 | 'com.apple.speech.SpeechDataInstallerd': 'Utility', 493 | 'com.apple.speech.SpeechRecognitionServer': 'Utility', 494 | 'com.apple.STMFramework.UIHelper': 'Utility', 495 | 'com.apple.syncservices.ConflictResolver': 'Utility', 496 | 'com.apple.syncservices.syncuid': 'Utility', 497 | 'com.apple.accessibility.AXVisualSupportAgent': 'Utility', 498 | 'com.apple.AccessibilityOnboarding': 'Utility', 499 | 'com.apple.accessibility.DFRHUD': 'Utility', 500 | 'com.apple.accessibility.universalAccessAuthWarn': 'Utility', 501 | 'com.apple.coreservices.UASharedPasteboardProgressUI': 'Utility', 502 | 'com.apple.ChineseTextConverterService': 'Utility', 503 | 'com.apple.SummaryService': 'Utility', 504 | 505 | # Applications Without Info.plist 506 | # These are typically system apps or incomplete installations. Categorize as 'System' or 'Unknown' 507 | 'Bento|Craft.app': 'Unknown', 508 | 'My School.app': 'Unknown', 509 | 'Odio.app': 'Unknown', 510 | 'SchoolMate.app': 'Unknown', 511 | 'liquiddetectiond.app': 'System', 512 | 'monitorproduct': 'Utility', 513 | 'Cisco-Systems.Spark': 'Social Media', 514 | 'Cisco-Systems.Spark': 'Social Media', # Duplicate, same category 515 | 'WebEx-PT.webexAppLauncher': 'Social Media', 516 | 'us.zoom.ZoomAutoUpdater': 'Marketing', 517 | 'com.apple.systemevents': 'Utility', 518 | 'com.apple.windowmanager.ShowDesktopEducation': 'Utility', 519 | 'com.apple.QuickLookDaemon': 'Utility', 520 | # ... add any remaining apps with "Info.plist not found" 521 | 522 | # Developer Builds and Derived Data 523 | 'com.Ai.NeuroNote3': 'Development', 524 | # Additional Derived Data entries categorized as 'Development' 525 | 'com.apple.BuildWebPage': 'Development', 526 | 'com.apple.MakePDF': 'Development', 527 | 'com.apple.AirScanScanner': 'Utility', 528 | 'com.apple.50onPaletteIM': 'Utility', 529 | 'com.apple.inputmethod.Ainu': 'Utility', 530 | 'com.apple.inputmethod.AssistiveControl': 'Utility', 531 | 'com.apple.CharacterPaletteIM': 'Utility', 532 | 'com.apple.inputmethod.ironwood': 'Utility', 533 | 'com.apple.EmojiFunctionRowItem-Container': 'Utility', 534 | 'com.apple.JapaneseIM.KanaTyping': 'Utility', 535 | 'com.apple.JapaneseIM.RomajiTyping': 'Utility', 536 | 'com.apple.KIM-Container': 'Utility', 537 | 'com.apple.inputmethod.PluginIM': 'Utility', 538 | 'com.apple.PAH-Container': 'Utility', 539 | 'com.apple.SCIM-Container': 'Utility', 540 | 'com.apple.TCIM-Container': 'Utility', 541 | 'com.apple.TYIM-Container': 'Utility', 542 | 'com.apple.inputmethod.Tamil': 'Utility', 543 | 'com.apple.TrackpadIM-Container': 'Utility', 544 | 'com.apple.TransliterationIM-Container': 'Utility', 545 | 'com.apple.VIM-Container': 'Utility', 546 | # ... continue for all derived data apps 547 | 548 | # Dotnet Shared Apps (System Utilities) 549 | 'com.dotnet.shared.Microsoft.AspNetCore.App': 'System', 550 | 'com.dotnet.shared.Microsoft.NETCore.App': 'System', 551 | # ... continue as needed 552 | 553 | # Default Category for Uncategorized Apps 554 | # Any Bundle ID not explicitly listed will be categorized as 'Unknown' 555 | } 556 | 557 | # Apply categories to the DataFrame 558 | df['category'] = df['app'].map(app_categories).fillna('Other') 559 | 560 | # Optional: Add hierarchical categories 561 | parent_categories = { 562 | 'Development': 'Productive', 563 | 'Marketing': 'Productive', 564 | 'Creative': 'Productive', 565 | 'Social Media': 'Distracting', 566 | 'Entertainment': 'Distracting', 567 | 'Utility': 'Neutral', 568 | 'Browsing': 'Neutral', 569 | 'AI Productivity': 'Productive', 570 | 'Other': 'Unknown' 571 | } 572 | df['parent_category'] = df['category'].map(parent_categories).fillna('Other') 573 | 574 | return df -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import streamlit as st 4 | import matplotlib.pyplot as plt 5 | import xml.etree.ElementTree as ET 6 | from pydantic import BaseModel 7 | import seaborn as sns 8 | from categories import categorize_apps 9 | 10 | class DataFrames(BaseModel): 11 | screen_df: pd.DataFrame 12 | heart_rate_df: pd.DataFrame 13 | sleep_df: pd.DataFrame 14 | audio_exposure_df: pd.DataFrame 15 | sleep_grouped: pd.DataFrame 16 | screen_grouped: pd.DataFrame 17 | productivity_usage: pd.DataFrame 18 | audio_grouped: pd.DataFrame 19 | 20 | class Config: 21 | arbitrary_types_allowed = True 22 | 23 | def ensure_utc(df, col): 24 | """ 25 | Ensure all datetime column is in the specified DataFrames are in UTC timezone. 26 | """ 27 | if df[col].dt.tz is None: 28 | df[col] = df[col].dt.tz_localize('UTC') 29 | df[col] = df[col].dt.tz_convert('UTC') 30 | 31 | def ensure_all_utc(dfs: list[pd.DataFrame], cols: list[str]): 32 | """ 33 | Ensure all datetime columns in the specified DataFrames are in UTC timezone. 34 | """ 35 | for df in dfs: 36 | for col in cols: 37 | try: 38 | ensure_utc(df, col) 39 | except: 40 | continue 41 | 42 | @st.cache_resource 43 | def setup_data() -> DataFrames: 44 | """ 45 | Setup the data for analysis. 46 | """ 47 | asleep_values = [1, 3, 4, 5] 48 | data_dir = "./data" 49 | # Paths to your data files 50 | csv_files = [os.path.join(data_dir, file) for file in os.listdir(data_dir) if file.endswith(".csv")] 51 | healthkit_export_xml = next((os.path.join(data_dir, file) for file in os.listdir(data_dir) if file.endswith(".xml")), None) 52 | 53 | screentime_dfs = [parse_screentime_csv(csv_file) for csv_file in csv_files] 54 | screen_df = pd.concat(screentime_dfs, ignore_index=True).drop_duplicates() 55 | # Parse health data from the XML file 56 | heart_rate_df = parse_healthkit_export(healthkit_export_xml, 'HKQuantityTypeIdentifierHeartRate') 57 | sleep_df = parse_healthkit_export(healthkit_export_xml, 'HKCategoryTypeIdentifierSleepAnalysis') 58 | audio_exposure_df = parse_healthkit_export(healthkit_export_xml, 'HKQuantityTypeIdentifierHeadphoneAudioExposure') 59 | 60 | # Convert datetime columns and ensure they are in UTC 61 | screen_df['start_time'] = pd.to_datetime(screen_df['start_time']) 62 | screen_df['end_time'] = pd.to_datetime(screen_df['end_time']) 63 | 64 | ensure_all_utc([screen_df, heart_rate_df, sleep_df, audio_exposure_df], ['startDate', 'endDate', "start_time", "end_time"]) 65 | 66 | # Convert 'value' to numeric type 67 | sleep_df['value'] = pd.to_numeric(sleep_df['value'], errors='coerce') 68 | 69 | # Map numeric codes to sleep state labels 70 | sleep_value_mapping = { 71 | 0: 'InBed', 72 | 1: 'AsleepUnspecified', 73 | 2: 'Awake', 74 | 3: 'AsleepCore', 75 | 4: 'AsleepDeep', 76 | 5: 'AsleepREM' 77 | } 78 | sleep_df['sleep_state'] = sleep_df['value'].map(sleep_value_mapping) 79 | 80 | # Calculate sleep duration in hours 81 | sleep_df['duration'] = (sleep_df['endDate'] - sleep_df['startDate']).dt.total_seconds() / 3600 82 | 83 | # Adjust sleep data to the next day 84 | sleep_df['date'] = sleep_df['startDate'].dt.date + pd.Timedelta(days=1) 85 | sleep_grouped = sleep_df.groupby('date').agg({'duration': 'sum'}).reset_index() 86 | 87 | # Group screen time usage by date 88 | screen_df['date'] = screen_df['start_time'].dt.date 89 | screen_df = categorize_apps(screen_df) 90 | screen_grouped = screen_df.groupby('date').agg({'usage': 'sum'}).reset_index() 91 | 92 | # Productivity apps list 93 | productivity_usage = screen_df[screen_df['parent_category'] == 'Productive'].groupby('date')['usage'].sum().reset_index() 94 | productivity_usage['usage_hours'] = productivity_usage['usage'] / 3600 95 | 96 | # Process audio exposure data 97 | audio_exposure_df['date'] = audio_exposure_df['startDate'].dt.date 98 | audio_grouped = audio_exposure_df.groupby('date').agg({'value': 'mean'}).reset_index() 99 | audio_grouped.rename(columns={'value': 'audio_exposure'}, inplace=True) 100 | 101 | return DataFrames(screen_df=screen_df, heart_rate_df=heart_rate_df, sleep_df=sleep_df, audio_exposure_df=audio_exposure_df, sleep_grouped=sleep_grouped, screen_grouped=screen_grouped, productivity_usage=productivity_usage, audio_grouped=audio_grouped) 102 | 103 | # Function to parse screentime CSV 104 | @st.cache_data 105 | def parse_screentime_csv(csv_file): 106 | """ 107 | Parses the screentime CSV file and returns a DataFrame. 108 | """ 109 | screentime_df = pd.read_csv(csv_file) 110 | screentime_df['start_time'] = pd.to_datetime(screentime_df['start_time']) 111 | screentime_df['end_time'] = pd.to_datetime(screentime_df['end_time']) 112 | return screentime_df 113 | 114 | @st.cache_data 115 | def parse_healthkit_export(xml_file, data_type): 116 | """ 117 | Parses the HealthKit XML export file and returns a DataFrame of the specified data type. 118 | """ 119 | tree = ET.parse(xml_file) 120 | root = tree.getroot() 121 | 122 | # Extract records of the specified type 123 | health_data = [] 124 | for record in root.findall("Record"): 125 | if record.attrib.get('type') == data_type: 126 | health_data.append(record.attrib) 127 | 128 | # Convert to DataFrame 129 | health_df = pd.DataFrame(health_data) 130 | 131 | # Convert timestamp columns 132 | if 'startDate' in health_df.columns: 133 | health_df['startDate'] = pd.to_datetime(health_df['startDate']) 134 | if 'endDate' in health_df.columns: 135 | health_df['endDate'] = pd.to_datetime(health_df['endDate']) 136 | # Convert 'value' column to numeric if possible 137 | if 'value' in health_df.columns: 138 | health_df['value'] = pd.to_numeric(health_df['value'], errors='coerce') 139 | return health_df 140 | 141 | @st.cache_data 142 | def peak_usage_times(screen_df): 143 | """ 144 | Gets the largest usage times of the day. 145 | """ 146 | st.title('Peak Usage Times') 147 | 148 | # Peak usage times 149 | screen_df['hour'] = screen_df['start_time'].dt.hour 150 | peak_usage_times = screen_df.groupby('hour')['usage'].sum().reset_index() 151 | 152 | # Convert usage to hours for better readability 153 | peak_usage_times['usage_hours'] = peak_usage_times['usage'] / 3600 154 | 155 | # Plot peak usage times 156 | fig, ax = plt.subplots(figsize=(10, 6)) 157 | ax.bar(peak_usage_times['hour'], peak_usage_times['usage_hours'], width=0.8, align='center') 158 | ax.set_xlabel('Hour of the Day') 159 | ax.set_ylabel('Total Usage (hours)') 160 | ax.set_title('Peak Usage Times') 161 | ax.grid(True) 162 | 163 | st.pyplot(fig) 164 | 165 | @st.cache_data 166 | def screen_time_analysis(screen_df): 167 | """ 168 | Analyzes the screen time data. 169 | """ 170 | st.title('Screen Time Data Analysis') 171 | 172 | # Display Screen Time DataFrame 173 | st.subheader('Screen Time Data') 174 | 175 | # Group by date and category 176 | category_usage = screen_df.groupby(['date', 'parent_category'])['usage'].sum().reset_index() 177 | category_usage['usage_hours'] = category_usage['usage'] / 3600 178 | 179 | # Pivot the DataFrame for easier plotting 180 | category_pivot = category_usage.pivot(index='date', columns='parent_category', values='usage_hours').fillna(0) 181 | 182 | # Plot stacked bar chart 183 | fig, ax = plt.subplots(figsize=(12, 6)) 184 | category_pivot.plot(kind='bar', stacked=True, ax=ax) 185 | ax.set_xlabel('Date') 186 | ax.set_ylabel('Usage (hours)') 187 | ax.set_title('Daily Usage by App Category') 188 | plt.xticks(rotation=45) 189 | st.pyplot(fig) 190 | 191 | 192 | total_usage_per_app = screen_df.groupby('app')['usage'].sum().reset_index() 193 | total_usage_per_app = total_usage_per_app.sort_values(by='usage', ascending=False) 194 | 195 | # Limit to top 10 apps by usage 196 | top_apps = total_usage_per_app.head(4)['app'].tolist() 197 | screen_df_top = screen_df[screen_df['app'].isin(top_apps)] 198 | 199 | # Aggregate usage by day for better readability 200 | screen_df_top['date'] = screen_df_top['start_time'].dt.date 201 | daily_usage = screen_df_top.groupby(['date', 'app'])['usage'].sum().unstack().fillna(0) 202 | 203 | # Plot total usage per app 204 | fig1, ax1 = plt.subplots(figsize=(10, 6)) 205 | daily_usage.plot(ax=ax1) 206 | ax1.set_xlabel('Date') 207 | ax1.set_ylabel('Total Usage (seconds)') 208 | ax1.set_title('Total Usage per App') 209 | plt.xticks(rotation=45) 210 | ax1.legend(title='App', bbox_to_anchor=(1.05, 1), loc='upper left') 211 | st.pyplot(fig1) 212 | 213 | # Peak usage times 214 | screen_df['hour'] = screen_df['start_time'].dt.hour 215 | peak_usage_times = screen_df.groupby('hour')['usage'].sum().reset_index() 216 | peak_usage_times['usage_hours'] = peak_usage_times['usage'] / 3600 217 | 218 | # Plot peak usage times 219 | fig2, ax2 = plt.subplots(figsize=(10, 6)) 220 | ax2.bar(peak_usage_times['hour'], peak_usage_times['usage_hours'], width=0.8, align='center') 221 | ax2.set_xlabel('Hour of the Day') 222 | ax2.set_ylabel('Total Usage (hours)') 223 | ax2.set_title('Peak Usage Times') 224 | ax2.grid(True) 225 | st.pyplot(fig2) 226 | 227 | st.title('Usage Patterns Over Time') 228 | 229 | # Group by hour and category 230 | hourly_usage = screen_df.groupby(['hour', 'parent_category'])['usage'].sum().reset_index() 231 | hourly_usage['usage_hours'] = hourly_usage['usage'] / 3600 232 | 233 | # Pivot for heatmap 234 | heatmap_data = hourly_usage.pivot(index='hour', columns='parent_category', values='usage_hours').fillna(0) 235 | 236 | # Plot heatmap 237 | fig, ax = plt.subplots(figsize=(12, 8)) 238 | sns.heatmap(heatmap_data, annot=True, fmt=".1f", cmap='YlGnBu', ax=ax) 239 | ax.set_title('Hourly Usage Heatmap by Category') 240 | st.pyplot(fig) 241 | 242 | 243 | # Total usage per app 244 | with st.expander(label="Data"): 245 | 246 | # Basic Statistics 247 | st.subheader('Basic Statistics') 248 | st.write(screen_df.describe()) 249 | 250 | st.subheader('Daily Usage by Category') 251 | st.write(category_pivot) 252 | 253 | st.subheader('Total Usage per App') 254 | 255 | st.write(total_usage_per_app) 256 | 257 | 258 | st.subheader('Peak Usage Times') 259 | st.write(heatmap_data) 260 | 261 | # Summary 262 | st.subheader('Summary of Analysis') 263 | summary = { 264 | 'total_usage': screen_df['usage'].sum(), 265 | 'average_session_length': screen_df['usage'].mean(), 266 | 'most_used_app': total_usage_per_app.iloc[0]['app'], 267 | 'peak_usage_hour': peak_usage_times['hour'][peak_usage_times['usage'].idxmax()] 268 | } 269 | st.write(summary) 270 | 271 | @st.cache_data 272 | def peak_usage_and_heart_rate(screen_df, heart_rate_df): 273 | """ 274 | Compares heart rate and screen time usage. 275 | """ 276 | st.title('Peak Usage Times and Heart Rate') 277 | 278 | # Aggregate screen time usage by hour 279 | screen_df['hour'] = screen_df['start_time'].dt.hour 280 | hourly_screen_usage = screen_df.groupby('hour')['usage'].sum().reset_index() 281 | hourly_screen_usage['usage_hours'] = hourly_screen_usage['usage'] / 3600 282 | 283 | # Aggregate heart rate by hour 284 | heart_rate_df['hour'] = heart_rate_df['startDate'].dt.hour 285 | hourly_heart_rate = heart_rate_df.groupby('hour')['value'].mean().reset_index() 286 | 287 | # Plot screen time and heart rate 288 | fig, ax1 = plt.subplots(figsize=(10, 6)) 289 | 290 | color = 'tab:blue' 291 | ax1.set_xlabel('Hour of the Day') 292 | ax1.set_ylabel('Screen Time Usage (hours)', color=color) 293 | ax1.bar(hourly_screen_usage['hour'], hourly_screen_usage['usage_hours'], color=color, alpha=0.6, label='Screen Time') 294 | ax1.tick_params(axis='y', labelcolor=color) 295 | 296 | ax2 = ax1.twinx() 297 | color = 'tab:red' 298 | ax2.set_ylabel('Heart Rate (count/min)', color=color) 299 | ax2.plot(hourly_heart_rate['hour'], hourly_heart_rate['value'], color=color, marker='o', linestyle='-', label='Heart Rate') 300 | ax2.tick_params(axis='y', labelcolor=color) 301 | 302 | fig.tight_layout() 303 | plt.title('Screen Time Usage and Heart Rate by Hour') 304 | ax1.grid(True) 305 | st.pyplot(fig) 306 | 307 | @st.cache_data 308 | def heart_rate_analysis(heart_rate_df, screen_df): 309 | st.title('Heart Rate Data Analysis') 310 | 311 | # Display Heart Rate DataFrame 312 | st.subheader('Heart Rate Data') 313 | st.write(heart_rate_df) 314 | 315 | # Basic Statistics 316 | st.subheader('Basic Statistics') 317 | st.write(heart_rate_df.describe()) 318 | 319 | # Correlate screen time with heart rate data 320 | st.subheader('Correlation Analysis with Heart Rate') 321 | merged_df = pd.merge_asof(screen_df.sort_values('start_time'), heart_rate_df.sort_values('startDate'), left_on='start_time', right_on='startDate') 322 | correlation = merged_df[['usage', 'value']].corr() 323 | st.write(correlation) 324 | 325 | # Plot peak usage times and heart rate 326 | peak_usage_and_heart_rate(screen_df, heart_rate_df) 327 | 328 | # Plot heart rate vs screen time usage 329 | fig4, ax4 = plt.subplots(figsize=(10, 6)) 330 | ax4.scatter(merged_df['value'], merged_df['usage']) 331 | ax4.set_xlabel('Heart Rate (count/min)') 332 | ax4.set_ylabel('Screen Time Usage (seconds)') 333 | ax4.set_title('Heart Rate vs Screen Time Usage') 334 | st.pyplot(fig4) 335 | 336 | 337 | @st.cache_data 338 | def sleep_analysis(sleep_df, screen_df, heart_rate_df, screen_grouped): 339 | """ 340 | Analyze sleep data in relation to screen time and heart rate, including daily sleep patterns, 341 | correlations with screen time, and hourly patterns. 342 | """ 343 | # Calculate sleep duration in hours 344 | sleep_df['duration'] = (sleep_df['endDate'] - sleep_df['startDate']).dt.total_seconds() / 3600 345 | 346 | # Adjust sleep data to the next day to better align with daily cycles 347 | sleep_df['date'] = sleep_df['startDate'].dt.date + pd.Timedelta(days=1) 348 | 349 | # Group sleep data by date 350 | sleep_grouped = sleep_df.groupby('date').agg({'duration': 'sum'}).reset_index() 351 | 352 | # Group screen time usage by hour 353 | screen_df['hour'] = screen_df['start_time'].dt.hour 354 | hourly_screen_usage = screen_df.groupby('hour')['usage'].sum().reset_index() 355 | hourly_screen_usage['usage_hours'] = hourly_screen_usage['usage'] / 3600 356 | 357 | # Group heart rate data by hour 358 | heart_rate_df['hour'] = heart_rate_df['startDate'].dt.hour 359 | hourly_heart_rate = heart_rate_df.groupby('hour')['value'].mean().reset_index() 360 | 361 | # Display comprehensive analysis 362 | st.title('Comprehensive Sleep Data Analysis') 363 | 364 | # Plot sleep duration over time 365 | st.subheader('Sleep Duration Over Time') 366 | fig1, ax1 = plt.subplots(figsize=(10, 6)) 367 | ax1.plot(sleep_grouped['date'], sleep_grouped['duration'], marker='o') 368 | ax1.set_xlabel('Date') 369 | ax1.set_ylabel('Sleep Duration (hours)') 370 | ax1.set_title('Sleep Duration Over Time') 371 | ax1.grid(True) 372 | plt.xticks(rotation=45) 373 | st.pyplot(fig1) 374 | 375 | # Correlation analysis with screen time 376 | st.subheader('Correlation Analysis with Screen Time') 377 | merged_sleep_df = pd.merge(screen_grouped, sleep_grouped, on='date', how='inner') 378 | correlation_sleep = merged_sleep_df[['usage', 'duration']].corr() 379 | st.write(correlation_sleep) 380 | 381 | # Scatter plot for sleep vs. screen time usage 382 | st.subheader('Sleep Duration vs Screen Time Usage') 383 | fig2, ax2 = plt.subplots(figsize=(10, 6)) 384 | ax2.scatter(merged_sleep_df['duration'], merged_sleep_df['usage'], color='purple', alpha=0.7) 385 | ax2.set_xlabel('Sleep Duration (hours)') 386 | ax2.set_ylabel('Screen Time Usage (seconds)') 387 | ax2.set_title('Sleep Duration vs Screen Time Usage') 388 | ax2.grid(True) 389 | st.pyplot(fig2) 390 | 391 | # Heatmap: Hourly sleep vs. screen time 392 | st.subheader('Hourly Sleep vs. Screen Time Heatmap') 393 | 394 | # Ensure 'usage_hours' column is numeric and fill missing values 395 | hourly_screen_usage['usage_hours'] = pd.to_numeric(hourly_screen_usage['usage_hours'], errors='coerce').fillna(0) 396 | 397 | # Create the pivot table for the heatmap 398 | heatmap_data = hourly_screen_usage.pivot_table(values='usage_hours', index='hour', aggfunc='sum') 399 | 400 | # Plot the heatmap 401 | fig3, ax3 = plt.subplots(figsize=(10, 6)) 402 | sns.heatmap(heatmap_data, cmap='Blues', annot=True, fmt='.1f', ax=ax3) 403 | 404 | ax3.set_title("Hourly Sleep vs. Screen Time Usage") 405 | st.pyplot(fig3) 406 | 407 | # Overlay heart rate and screen time usage throughout the day 408 | st.subheader('Hourly Heart Rate and Screen Time Usage') 409 | fig4, ax4 = plt.subplots(figsize=(10, 6)) 410 | 411 | # Bar plot for screen time usage 412 | ax4.bar(hourly_screen_usage['hour'], hourly_screen_usage['usage_hours'], color='blue', alpha=0.6, label='Screen Time Usage (hours)') 413 | ax4.set_xlabel('Hour of the Day') 414 | ax4.set_ylabel('Screen Time Usage (hours)', color='blue') 415 | ax4.tick_params(axis='y', labelcolor='blue') 416 | ax4.legend(loc='upper left') 417 | 418 | # Line plot for heart rate 419 | ax5 = ax4.twinx() 420 | ax5.plot(hourly_heart_rate['hour'], hourly_heart_rate['value'], color='red', marker='o', linestyle='-', label='Heart Rate (count/min)') 421 | ax5.set_ylabel('Heart Rate (count/min)', color='red') 422 | ax5.tick_params(axis='y', labelcolor='red') 423 | ax5.legend(loc='upper right') 424 | 425 | ax4.set_title('Heart Rate and Screen Time Usage by Hour of the Day') 426 | ax4.grid(True) 427 | fig4.tight_layout() 428 | st.pyplot(fig4) 429 | 430 | # Display Sleep DataFrame 431 | st.subheader('Sleep Data') 432 | st.write(sleep_df) 433 | 434 | 435 | @st.cache_data 436 | def additional_insights(screen_df, audio_exposure_df): 437 | st.title('Additional Insights') 438 | 439 | # Daily Screen Time Patterns 440 | st.subheader('Daily Screen Time Patterns') 441 | daily_screen_usage = screen_df.groupby('date')['usage'].sum().reset_index() 442 | daily_screen_usage['usage_hours'] = daily_screen_usage['usage'] / 3600 443 | 444 | fig1, ax1 = plt.subplots(figsize=(10, 6)) 445 | ax1.plot(daily_screen_usage['date'], daily_screen_usage['usage_hours'], marker='o') 446 | ax1.set_xlabel('Date') 447 | ax1.set_ylabel('Screen Time Usage (hours)') 448 | ax1.set_title('Daily Screen Time Usage Patterns') 449 | plt.xticks(rotation=45) 450 | ax1.grid(True) 451 | st.pyplot(fig1) 452 | 453 | # Productivity vs. Music Loudness 454 | st.subheader('Productivity vs. Music Loudness') 455 | productivity_apps = ['com.microsoft.VSCode', 'com.apple.dt.Xcode', 'company.thebrowser.Browser', 'com.openai.chat'] 456 | 457 | screen_df['parent_category'] = screen_df['app'].apply(lambda x: 'Productive' if x in productivity_apps else 'Other') 458 | productivity_usage = screen_df[screen_df['parent_category'] == 'Productive'].groupby('date')['usage'].sum().reset_index() 459 | productivity_usage['usage_hours'] = productivity_usage['usage'] / 3600 460 | 461 | audio_exposure_df['date'] = audio_exposure_df['startDate'].dt.date 462 | daily_audio_exposure = audio_exposure_df.groupby('date')['value'].mean().reset_index() 463 | 464 | # Merge DataFrames 465 | merged_productivity_audio = pd.merge(productivity_usage, daily_audio_exposure, on='date', how='inner') 466 | correlation_audio = merged_productivity_audio[['usage_hours', 'value']].corr() 467 | 468 | st.write(correlation_audio) 469 | 470 | fig2, ax2 = plt.subplots(figsize=(10, 6)) 471 | ax2.scatter(merged_productivity_audio['value'], merged_productivity_audio['usage_hours']) 472 | ax2.set_xlabel('Average Audio Exposure (dBASPL)') 473 | ax2.set_ylabel('Productivity App Usage (hours)') 474 | ax2.set_title('Productivity App Usage vs. Average Audio Exposure') 475 | st.pyplot(fig2) 476 | 477 | @st.cache_data 478 | def productivity_analysis(sleep_grouped, productivity_usage, audio_grouped): 479 | st.title('Productivity Analysis') 480 | 481 | # Ensure 'date' columns are datetime 482 | sleep_grouped['date'] = pd.to_datetime(sleep_grouped['date']) 483 | productivity_usage['date'] = pd.to_datetime(productivity_usage['date']) 484 | audio_grouped['date'] = pd.to_datetime(audio_grouped['date']) 485 | 486 | # Create sleep_grouped_next_day by shifting the 'date' back by one day 487 | sleep_grouped_next_day = sleep_grouped.copy() 488 | sleep_grouped_next_day['date'] = sleep_grouped_next_day['date'] - pd.Timedelta(days=1) 489 | 490 | # Merge DataFrames for analysis 491 | merged_df_same_day = productivity_usage.merge(sleep_grouped, on='date', how='left') 492 | merged_df_next_day = productivity_usage.merge(sleep_grouped_next_day, on='date', how='left') 493 | merged_df_audio = productivity_usage.merge(audio_grouped, on='date', how='left') 494 | 495 | # Rename columns for clarity 496 | merged_df_same_day.rename(columns={'duration': 'same_day_sleep_duration'}, inplace=True) 497 | merged_df_next_day.rename(columns={'duration': 'next_day_sleep_duration'}, inplace=True) 498 | 499 | st.subheader('Productivity vs. Same Day Sleep Duration') 500 | fig1, ax1 = plt.subplots(figsize=(10, 6)) 501 | ax1.scatter(merged_df_same_day['same_day_sleep_duration'], merged_df_same_day['usage_hours']) 502 | ax1.set_xlabel('Same Day Sleep Duration (hours)') 503 | ax1.set_ylabel('Productivity App Usage (hours)') 504 | ax1.set_title('Productivity vs. Same Day Sleep Duration') 505 | st.pyplot(fig1) 506 | 507 | st.subheader('Productivity vs. Next Day Sleep Duration') 508 | fig2, ax2 = plt.subplots(figsize=(10, 6)) 509 | ax2.scatter(merged_df_next_day['next_day_sleep_duration'], merged_df_next_day['usage_hours']) 510 | ax2.set_xlabel('Next Day Sleep Duration (hours)') 511 | ax2.set_ylabel('Productivity App Usage (hours)') 512 | ax2.set_title('Productivity vs. Next Day Sleep Duration') 513 | st.pyplot(fig2) 514 | 515 | st.subheader('Productivity vs. Audio Exposure') 516 | fig3, ax3 = plt.subplots(figsize=(10, 6)) 517 | ax3.scatter(merged_df_audio['audio_exposure'], merged_df_audio['usage_hours']) 518 | ax3.set_xlabel('Average Audio Exposure (dBASPL)') 519 | ax3.set_ylabel('Productivity App Usage (hours)') 520 | ax3.set_title('Productivity vs. Audio Exposure') 521 | st.pyplot(fig3) 522 | 523 | st.subheader('Correlation Analysis') 524 | correlation_same_day = merged_df_same_day[['usage_hours', 'same_day_sleep_duration']].corr() 525 | correlation_next_day = merged_df_next_day[['usage_hours', 'next_day_sleep_duration']].corr() 526 | correlation_audio = merged_df_audio[['usage_hours', 'audio_exposure']].corr() 527 | 528 | st.write("Correlation with Same Day Sleep Duration") 529 | st.write(correlation_same_day) 530 | st.write("Correlation with Next Day Sleep Duration") 531 | st.write(correlation_next_day) 532 | st.write("Correlation with Audio Exposure") 533 | st.write(correlation_audio) 534 | 535 | # Insights summary 536 | st.subheader('Insights Summary') 537 | st.write('This analysis provides insights into how your productivity is influenced by sleep duration and audio exposure. Key correlations and trends are highlighted.') 538 | 539 | @st.cache_data 540 | def productivity_metrics(screen_df): 541 | st.title('Productivity Metrics') 542 | 543 | # Total usage per day 544 | total_daily_usage = screen_df.groupby('date')['usage'].sum().reset_index() 545 | total_daily_usage['usage_hours'] = total_daily_usage['usage'] / 3600 546 | 547 | # Productive usage per day 548 | productive_daily_usage = screen_df[screen_df['parent_category'] == 'Productive'].groupby('date')['usage'].sum().reset_index() 549 | productive_daily_usage['usage_hours'] = productive_daily_usage['usage'] / 3600 550 | 551 | # Merge DataFrames 552 | daily_productivity = pd.merge(total_daily_usage[['date', 'usage_hours']], productive_daily_usage[['date', 'usage_hours']], on='date', how='left', suffixes=('_total', '_productive')).fillna(0) 553 | 554 | # Calculate productivity ratio 555 | daily_productivity['productivity_ratio'] = (daily_productivity['usage_hours_productive'] / daily_productivity['usage_hours_total']) * 100 556 | 557 | # Calculate averages 558 | avg_productive_hours = daily_productivity['usage_hours_productive'].mean() 559 | avg_productivity_ratio = daily_productivity['productivity_ratio'].mean() 560 | 561 | # Ensure daily_productivity DataFrame exists 562 | if 'daily_productivity' not in locals(): 563 | # Total usage per day 564 | total_daily_usage = screen_df.groupby('date')['usage'].sum().reset_index() 565 | total_daily_usage['usage_hours'] = total_daily_usage['usage'] / 3600 566 | 567 | # Productive usage per day 568 | productive_daily_usage = screen_df[screen_df['parent_category'] == 'Productive'].groupby('date')['usage'].sum().reset_index() 569 | productive_daily_usage['usage_hours'] = productive_daily_usage['usage'] / 3600 570 | 571 | # Merge DataFrames 572 | daily_productivity = pd.merge( 573 | total_daily_usage[['date', 'usage_hours']], 574 | productive_daily_usage[['date', 'usage_hours']], 575 | on='date', 576 | how='left', 577 | suffixes=('_total', '_productive') 578 | ).fillna(0) 579 | 580 | # Calculate productivity ratio 581 | daily_productivity['productivity_ratio'] = (daily_productivity['usage_hours_productive'] / daily_productivity['usage_hours_total']) * 100 582 | 583 | # Calculate 7-day rolling average for productivity ratio 584 | daily_productivity['rolling_productivity_ratio'] = daily_productivity['productivity_ratio'].rolling(window=7, min_periods=1).mean() 585 | 586 | # Plot productivity ratio over time 587 | fig, ax = plt.subplots(figsize=(12, 6)) 588 | ax.plot(daily_productivity['date'], daily_productivity['productivity_ratio'], label='Daily Productivity Ratio') 589 | ax.plot(daily_productivity['date'], daily_productivity['rolling_productivity_ratio'], label='7-Day Rolling Average', linestyle='--') 590 | ax.set_xlabel('Date') 591 | ax.set_ylabel('Productivity Ratio (%)') 592 | ax.set_title('Productivity Ratio Over Time') 593 | plt.xticks(rotation=45) 594 | ax.legend() 595 | st.pyplot(fig) 596 | 597 | # Analyze productivity by day of the week 598 | daily_productivity['day_of_week'] = pd.to_datetime(daily_productivity['date']).dt.day_name() 599 | productivity_by_day = daily_productivity.groupby('day_of_week')['productivity_ratio'].mean().reindex([ 600 | 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']) 601 | 602 | # Plot productivity by day of the week 603 | fig2, ax2 = plt.subplots(figsize=(10, 6)) 604 | ax2.bar(productivity_by_day.index, productivity_by_day.values) 605 | ax2.set_xlabel('Day of the Week') 606 | ax2.set_ylabel('Average Productivity Ratio (%)') 607 | ax2.set_title('Average Productivity by Day of the Week') 608 | plt.xticks(rotation=45) 609 | st.pyplot(fig2) 610 | 611 | # Display metrics 612 | st.subheader('Daily Productivity Metrics') 613 | st.write(daily_productivity) 614 | 615 | st.write(screen_df.head()) 616 | 617 | st.subheader('Average Productivity') 618 | st.write(f"Average Daily Productive Hours: {avg_productive_hours:.2f} hours") 619 | st.write(f"Average Productivity Ratio: {avg_productivity_ratio:.2f}%") 620 | 621 | sidebar = st.sidebar 622 | sidebar.title("HealthKit and Screen Time Data Analyzer") 623 | sidebar.subheader("Understand your digital habits and health data.") 624 | 625 | 626 | data = setup_data() 627 | 628 | page = sidebar.radio("Go to", ["Screen Time Analysis", "Sleep Analysis", "Productivity Metrics", "Productivity Analysis", "Heart Rate Analysis", "Additional Insights"]) 629 | 630 | sidebar.markdown(""" 631 | --- 632 | Created by **Andreas Ink** 633 | """) 634 | 635 | sidebar.markdown(""" 636 | [Github](https://github.com/AndreasInk) 637 | [LinkedIn](https://www.linkedin.com/in/andreas-ink/) 638 | """) 639 | 640 | if page == "Screen Time Analysis": 641 | screen_time_analysis(data.screen_df) 642 | elif page == "Heart Rate Analysis": 643 | heart_rate_analysis(data.heart_rate_df, data.screen_df) 644 | elif page == "Sleep Analysis": 645 | sleep_analysis(data.sleep_df, data.screen_df, data.heart_rate_df, data.screen_grouped) 646 | elif page == "Productivity Analysis": 647 | productivity_analysis(data.sleep_grouped, data.productivity_usage, data.audio_grouped) 648 | elif page == "Productivity Metrics": 649 | productivity_metrics(data.screen_df) 650 | elif page == "Additional Insights": 651 | additional_insights(data.screen_df, data.audio_exposure_df) 652 | 653 | --------------------------------------------------------------------------------