├── config.ini
├── src
├── __init__.py
├── stats
│ ├── __init__.py
│ ├── stepsStats.py
│ ├── hbStats.py
│ ├── combinedStats.py
│ └── sleepStats.py
├── test
│ ├── __init__.py
│ ├── testHbStats.py
│ ├── testPlotting.py
│ └── testSleepStats.py
├── resources
│ ├── __init__.py
│ ├── unittest
│ │ ├── test_sleep_basic01.csv
│ │ ├── test_sleep_basic02.csv
│ │ ├── test_hb_basic01.csv
│ │ ├── test_sleep_intervals01.csv
│ │ ├── test_sleep_intervals02.csv
│ │ └── test_sleepStats.csv
│ └── intraday_sleep_sample.csv
└── util
│ ├── __init__.py
│ ├── gather_keys_oauth2.py
│ ├── scraper.py
│ ├── utils.py
│ └── plotting.py
├── requirements.txt
├── .gitignore
├── setup.py
├── README.md
├── heartBeatTesting.ipynb
├── LICENSE
└── Sleep Dashboard.ipynb
/config.ini:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/stats/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/resources/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | RESOURCE_PATH = os.path.join(os.path.dirname(__file__))
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | numpy
2 | pandas
3 | matplotlib
4 | seaborn
5 | scipy
6 | scikit-learn
7 | requests
8 |
--------------------------------------------------------------------------------
/src/util/__init__.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | logging.basicConfig(level=logging.INFO)
4 | logger = logging.getLogger(__name__)
5 | logger.setLevel(logging.INFO)
6 |
--------------------------------------------------------------------------------
/src/stats/stepsStats.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import numpy as np
3 |
4 | NAME_VAL_COL = "value"
5 | NAME_DT_COL = "datetime"
6 |
7 | def groupAndSumByDate(stepsData):
8 | data = stepsData
9 | if isinstance(stepsData, list):
10 | data = pd.concat(stepsData, ignore_index=True)
11 | res = data.groupby(data[NAME_DT_COL].dt.date)[NAME_VAL_COL].sum()
12 | return res
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | *.py[cod]
3 |
4 | *.pydevproject
5 | .project
6 | .metadata
7 | bin/**
8 | tmp/**
9 | tmp/**/*
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 | *.egg-info
19 |
20 | # CDT-specific
21 | .cproject
22 |
23 | #PYCHARM
24 | .idea/
25 |
26 | #IPYNB
27 | *.ipynb_checkpoints
28 | src/test/devTesting.ipynb
29 |
30 |
--------------------------------------------------------------------------------
/src/resources/unittest/test_sleep_basic01.csv:
--------------------------------------------------------------------------------
1 | 2016-03-21 23:18:00,2
2 | 2016-03-21 23:19:00,2
3 | 2016-03-21 23:20:00,2
4 | 2016-03-21 23:21:00,2
5 | 2016-03-21 23:22:00,2
6 | 2016-03-21 23:23:00,3
7 | 2016-03-21 23:24:00,2
8 | 2016-03-21 23:25:00,1
9 | 2016-03-21 23:26:00,1
10 | 2016-03-21 23:27:00,1
11 | 2016-03-21 23:28:00,1
12 | 2016-03-21 23:29:00,1
13 | 2016-03-21 23:30:00,1
14 | 2016-03-21 23:31:00,1
15 | 2016-03-21 23:32:00,1
16 | 2016-03-21 23:33:00,1
--------------------------------------------------------------------------------
/src/resources/unittest/test_sleep_basic02.csv:
--------------------------------------------------------------------------------
1 | 2016-03-23 00:18:00,2
2 | 2016-03-23 00:19:00,2
3 | 2016-03-23 00:20:00,2
4 | 2016-03-23 00:21:00,2
5 | 2016-03-23 00:22:00,2
6 | 2016-03-23 00:23:00,3
7 | 2016-03-23 00:24:00,2
8 | 2016-03-23 00:25:00,1
9 | 2016-03-23 00:26:00,1
10 | 2016-03-23 00:27:00,1
11 | 2016-03-23 00:28:00,1
12 | 2016-03-23 00:29:00,1
13 | 2016-03-23 00:30:00,1
14 | 2016-03-23 00:31:00,1
15 | 2016-03-23 00:32:00,1
16 | 2016-03-23 00:33:00,1
--------------------------------------------------------------------------------
/src/resources/unittest/test_hb_basic01.csv:
--------------------------------------------------------------------------------
1 | 2016-03-21 23:18:00,60
2 | 2016-03-21 23:19:00,60
3 | 2016-03-21 23:20:00,60
4 | 2016-03-21 23:21:00,60
5 | 2016-03-21 23:22:00,60
6 | 2016-03-21 23:23:00,60
7 | 2016-03-21 23:24:00,60
8 | 2016-03-21 23:25:00,60
9 | 2016-03-21 23:26:00,60
10 | 2016-03-21 23:27:00,60
11 | 2016-03-21 23:28:00,50
12 | 2016-03-21 23:29:00,60
13 | 2016-03-21 23:30:00,60
14 | 2016-03-21 23:31:00,60
15 | 2016-03-21 23:32:00,60
16 | 2016-03-21 23:33:00,70
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 |
4 | setup(
5 | name="fitbit_scraper",
6 | version="0.1.0",
7 | description="Utility to scrape personal Fitbit data",
8 | license="Apache 2.0",
9 | author="Alex Martinelli",
10 | packages=find_packages(),
11 | entry_points={
12 | 'console_scripts': ['fitbit-scraper=src.util.scraper:main'],
13 | },
14 | install_requires=[
15 | 'fitbit',
16 | 'cherrypy'
17 | ],
18 | )
19 |
--------------------------------------------------------------------------------
/src/stats/hbStats.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 |
3 | NAME_VAL_COL = "value"
4 | NAME_DT_COL = "datetime"
5 |
6 | def basicStats(hbData):
7 | return hbData.describe()
8 |
9 | def groupByBasicStats(groupByFun, data, indexName=None):
10 | hbData = data.groupby(groupByFun)
11 |
12 | stats = {'count' : hbData[NAME_VAL_COL].count(),
13 | 'mean' : hbData[NAME_VAL_COL].mean(),
14 | 'min' : hbData[NAME_VAL_COL].min(),
15 | 'max' : hbData[NAME_VAL_COL].max(),
16 | 'std' : hbData[NAME_VAL_COL].std()
17 | }
18 |
19 | df = pd.DataFrame.from_dict(stats)
20 |
21 | if indexName:
22 | df.index.name = indexName
23 |
24 | return df.reset_index()
25 |
26 | def getMaxValues(data, n):
27 | return data.nlargest(n, NAME_VAL_COL)
28 |
29 | def getMinValues(data, n):
30 | return data.nsmallest(n, NAME_VAL_COL)
--------------------------------------------------------------------------------
/src/resources/unittest/test_sleep_intervals01.csv:
--------------------------------------------------------------------------------
1 | 2016-03-21 23:18:00,2
2 | 2016-03-21 23:19:00,2
3 | 2016-03-21 23:20:00,2
4 | 2016-03-21 23:21:00,2
5 | 2016-03-21 23:22:00,2
6 | 2016-03-21 23:23:00,3
7 | 2016-03-21 23:24:00,2
8 | 2016-03-21 23:25:00,1
9 | 2016-03-21 23:26:00,1
10 | 2016-03-21 23:27:00,1
11 | 2016-03-21 23:28:00,1
12 | 2016-03-21 23:29:00,2
13 | 2016-03-21 23:30:00,2
14 | 2016-03-21 23:31:00,1
15 | 2016-03-21 23:32:00,1
16 | 2016-03-21 23:33:00,1
17 | 2016-03-21 23:34:00,1
18 | 2016-03-21 23:35:00,1
19 | 2016-03-21 23:36:00,2
20 | 2016-03-21 23:37:00,2
21 | 2016-03-21 23:38:00,1
22 | 2016-03-21 23:39:00,1
23 | 2016-03-21 23:40:00,1
24 | 2016-03-21 23:41:00,1
25 | 2016-03-21 23:42:00,1
26 | 2016-03-21 23:43:00,1
27 | 2016-03-21 23:44:00,1
28 | 2016-03-21 23:45:00,1
29 | 2016-03-21 23:46:00,2
30 | 2016-03-21 23:47:00,1
31 | 2016-03-21 23:48:00,1
32 | 2016-03-21 23:49:00,1
33 | 2016-03-21 23:50:00,2
34 | 2016-03-21 23:51:00,1
35 | 2016-03-21 23:52:00,1
36 | 2016-03-21 23:53:00,1
37 | 2016-03-21 23:54:00,1
38 | 2016-03-21 23:55:00,1
39 | 2016-03-21 23:56:00,1
40 | 2016-03-21 23:57:00,1
41 | 2016-03-21 23:58:00,1
42 | 2016-03-21 23:59:00,2
43 | 2016-03-22 00:00:00,1
44 | 2016-03-22 00:01:00,1
--------------------------------------------------------------------------------
/src/resources/unittest/test_sleep_intervals02.csv:
--------------------------------------------------------------------------------
1 | 2016-03-22 23:18:00,2
2 | 2016-03-22 23:19:00,2
3 | 2016-03-22 23:20:00,2
4 | 2016-03-22 23:21:00,2
5 | 2016-03-22 23:22:00,2
6 | 2016-03-22 23:23:00,1
7 | 2016-03-22 23:24:00,1
8 | 2016-03-22 23:25:00,1
9 | 2016-03-22 23:26:00,1
10 | 2016-03-22 23:27:00,1
11 | 2016-03-22 23:28:00,1
12 | 2016-03-22 23:29:00,2
13 | 2016-03-22 23:30:00,2
14 | 2016-03-22 23:31:00,1
15 | 2016-03-22 23:32:00,1
16 | 2016-03-22 23:33:00,1
17 | 2016-03-22 23:34:00,1
18 | 2016-03-22 23:35:00,1
19 | 2016-03-22 23:36:00,2
20 | 2016-03-22 23:37:00,2
21 | 2016-03-22 23:38:00,1
22 | 2016-03-22 23:39:00,1
23 | 2016-03-22 23:40:00,1
24 | 2016-03-22 23:41:00,1
25 | 2016-03-22 23:42:00,1
26 | 2016-03-22 23:43:00,1
27 | 2016-03-22 23:44:00,1
28 | 2016-03-22 23:45:00,1
29 | 2016-03-22 23:46:00,2
30 | 2016-03-22 23:47:00,1
31 | 2016-03-22 23:48:00,1
32 | 2016-03-22 23:49:00,1
33 | 2016-03-22 23:50:00,2
34 | 2016-03-22 23:51:00,1
35 | 2016-03-22 23:52:00,1
36 | 2016-03-22 23:53:00,1
37 | 2016-03-22 23:54:00,1
38 | 2016-03-22 23:55:00,1
39 | 2016-03-22 23:56:00,1
40 | 2016-03-22 23:57:00,1
41 | 2016-03-22 23:58:00,1
42 | 2016-03-22 23:59:00,1
43 | 2016-03-23 00:00:00,1
44 | 2016-03-23 00:01:00,1
--------------------------------------------------------------------------------
/src/test/testHbStats.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from src.resources import RESOURCE_PATH
4 | from src.stats import hbStats
5 | from src.util import utils
6 | import pandas as pd
7 |
8 | class HbStatsTestCase(unittest.TestCase):
9 | def test_basicStats(self):
10 | filepath = RESOURCE_PATH + "\\unittest\\test_hb_basic01.csv"
11 | data = utils.loadIntradayData(filepath).set_index('datetime')
12 | stats = hbStats.groupByBasicStats(pd.TimeGrouper(freq='d'), data)
13 |
14 | self.assertEqual(stats.iloc[0]['count'], 16)
15 | self.assertEqual(stats.iloc[0]['max'], 70)
16 | self.assertEqual(stats.iloc[0]['min'], 50)
17 | self.assertEqual(stats.iloc[0]['mean'], 60)
18 |
19 | def test_minMax(self):
20 | filepath = RESOURCE_PATH + "\\unittest\\test_hb_basic01.csv"
21 | data = utils.loadIntradayData(filepath).set_index('datetime')
22 | stats = hbStats.getMaxValues(data, 2)
23 |
24 | self.assertEqual(stats.iloc[0].value, 70)
25 | self.assertEqual(stats.iloc[1].value, 60)
26 |
27 | stats = hbStats.getMinValues(data, 2)
28 |
29 | self.assertEqual(stats.iloc[0].value, 50)
30 | self.assertEqual(stats.iloc[1].value, 60)
31 |
32 | if __name__ == '__main__':
33 | unittest.main()
34 |
--------------------------------------------------------------------------------
/src/test/testPlotting.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import pandas as pd
3 |
4 | from src.resources import RESOURCE_PATH
5 | from src.stats import sleepStats
6 | from src.util import utils
7 | from src.util import plotting as mplot
8 |
9 | class SleepStatsTestCase(unittest.TestCase):
10 | def test_plottingOnBasicStats(self):
11 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_basic01.csv"
12 | data1 = utils.loadIntradayData(filepath)
13 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_basic02.csv"
14 | data2 = utils.loadIntradayData(filepath)
15 | stats = sleepStats.generateStatsFrom([data1, data2],
16 | sleepStats.STATS_NAME_BASIC_AND_TIMING).reset_index()
17 | stats['date'] = pd.to_datetime(stats['date'])
18 |
19 | mplot.plotYearAndMonthStatsSleep(stats)
20 | mplot.plotPreliminaryStats(stats)
21 | mplot.plotWeekdayStatsSleep(stats)
22 | mplot.plotDailyStatsSleep(stats)
23 | mplot.plotMonthlyStatsSleep(stats)
24 |
25 |
26 | def test_plottingOnIntradayStats(self):
27 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_basic01.csv"
28 | data1 = utils.loadIntradayData(filepath)
29 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_basic02.csv"
30 | data2 = utils.loadIntradayData(filepath)
31 | stats = sleepStats.generateStatsFrom([data1, data2],
32 | sleepStats.STATS_NAME_INTRADAY)
33 |
34 | data = stats.apply(pd.value_counts)
35 | mplot.plotSleepValueHeatmap(data, sleepValue=1)
36 |
37 | if __name__ == '__main__':
38 | unittest.main()
39 |
--------------------------------------------------------------------------------
/src/stats/combinedStats.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import pandas as pd
3 |
4 | def combineHbAndToggl(togglData, hbData):
5 | """
6 | Combine Toggl data and heart-rate data, creating a summary of heart-rate info
7 | for each Toggl entry (based on date and time correspondence)
8 | :param togglData: dataframe of toggl data
9 | :param hbData: heart-rate dataframe
10 | :return: resulting dataframe of original toggl data + hb stats
11 | """
12 |
13 | def computeHbStats(togglRow, hbData):
14 | hb = hbData[(hbData['datetime'] > togglRow['Start'])
15 | & (hbData['datetime'] < togglRow['End'])]['value']
16 | # hb = hb_data.between_time(toggl_row['Start'], toggl_row['End'])
17 | stats = {'count': hb.count(),
18 | 'mean': hb.mean(),
19 | 'min': hb.min(),
20 | 'max': hb.max(),
21 | 'std': hb.std()
22 | }
23 | return pd.Series(stats)
24 |
25 | # compute hb stats using previous function, and join to toggl data
26 | combinedStats = togglData.join(togglData.apply(lambda x: computeHbStats(x, hbData), axis=1))
27 |
28 | # remove entries for which no hb stats are present (any apart from count would work)
29 | combinedStats = combinedStats.dropna(how='all', subset=['max', 'mean', 'min', 'std'])
30 |
31 | return combinedStats
32 |
33 | def correlateSleepAndSteps(sleepStats, stepsStats, sleepStatName):
34 | return sleepStats[sleepStatName].corr(stepsStats)
35 |
36 | def tranformToGrowthFromStart(stats):
37 | res = stats.apply(lambda x: x / x[0])
38 | return res
39 |
40 | def tranformToGrowthFromPrevious(stats):
41 | res = stats.apply(lambda x: x - x.shift(1))
42 | return res
43 |
44 | def tranformToGrowthFromPreviousLog(stats):
45 | res = stats.apply(lambda x: np.log(x) - np.log(x.shift(1)))
46 | return res
47 |
48 | def normalize(row):
49 | return row - row.mean()
50 |
51 | def timeToInt(time):
52 | hour = time.hour
53 | return (hour * 60) + time.minute
54 |
55 | def pivotedTimeToInt(time, pivot, max_val=23):
56 | hour = time.hour
57 | if hour >= pivot:
58 | return ((hour - pivot) * 60) + time.minute
59 | else:
60 | return ((max_val - (pivot - hour) ) * 60) + time.minute
--------------------------------------------------------------------------------
/src/test/testSleepStats.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from src.resources import RESOURCE_PATH
4 | from src.stats import sleepStats
5 | from src.util import utils
6 | import datetime
7 |
8 | class SleepStatsTestCase(unittest.TestCase):
9 | def test_firstMinuteAsleep(self):
10 | filepath = RESOURCE_PATH + "\\unittest\\test_sleepStats.csv"
11 | data = utils.loadIntradayData(filepath)
12 | firstMinuteAsleep = sleepStats.getFirstMinuteAsleep(data)
13 | self.assertEqual(firstMinuteAsleep, 8)
14 |
15 | firstMinuteAsleep = sleepStats.getFirstMinuteAsleep(data, 10)
16 | self.assertEqual(firstMinuteAsleep, 8)
17 |
18 | firstMinuteAsleep = sleepStats.getFirstMinuteAsleep(data, 11)
19 | self.assertEqual(firstMinuteAsleep, 19)
20 |
21 | firstMinuteAsleep = sleepStats.getFirstMinuteAsleep(data, 500)
22 | self.assertEqual(firstMinuteAsleep, None)
23 |
24 | def test_basicStats(self):
25 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_basic01.csv"
26 | data1 = utils.loadIntradayData(filepath)
27 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_basic02.csv"
28 | data2 = utils.loadIntradayData(filepath)
29 | stats = sleepStats.generateBasicStats([data1, data2])
30 |
31 | self.assertEqual(stats.iloc[0].name, datetime.date(2016, 3, 21))
32 | self.assertEqual(stats.iloc[1].name, datetime.date(2016, 3, 22))
33 |
34 | def test_intradayStats(self):
35 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_basic01.csv"
36 | data1 = utils.loadIntradayData(filepath)
37 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_basic02.csv"
38 | data2 = utils.loadIntradayData(filepath)
39 | stats = sleepStats.generateIntradayStats([data1, data2])
40 |
41 | self.assertEqual(stats.iloc[0].name, datetime.date(2016, 3, 21))
42 | self.assertEqual(stats.iloc[1].name, datetime.date(2016, 3, 22))
43 |
44 | def test_intervalsStats(self):
45 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_intervals01.csv"
46 | data1 = utils.loadIntradayData(filepath)
47 | filepath = RESOURCE_PATH + "\\unittest\\test_sleep_intervals02.csv"
48 | data2 = utils.loadIntradayData(filepath)
49 | stats = sleepStats.generateIntervalsStats([data1, data2], 5)
50 |
51 | self.assertEqual(stats.iloc[0][0], 5)
52 | self.assertEqual(stats.iloc[0][1], 8)
53 | self.assertEqual(stats.iloc[0][2], 8)
54 | self.assertEqual(stats.iloc[1][0], 6)
55 | self.assertEqual(stats.iloc[1][1], 5)
56 | self.assertEqual(stats.iloc[1][2], 8)
57 | self.assertEqual(stats.iloc[1][3], 11)
58 |
59 | if __name__ == '__main__':
60 | unittest.main()
61 |
--------------------------------------------------------------------------------
/src/resources/unittest/test_sleepStats.csv:
--------------------------------------------------------------------------------
1 | 2016-03-21 23:18:00,2
2 | 2016-03-21 23:19:00,2
3 | 2016-03-21 23:20:00,2
4 | 2016-03-21 23:21:00,2
5 | 2016-03-21 23:22:00,2
6 | 2016-03-21 23:23:00,3
7 | 2016-03-21 23:24:00,2
8 | 2016-03-21 23:25:00,1
9 | 2016-03-21 23:26:00,1
10 | 2016-03-21 23:27:00,1
11 | 2016-03-21 23:28:00,1
12 | 2016-03-21 23:29:00,1
13 | 2016-03-21 23:30:00,1
14 | 2016-03-21 23:31:00,1
15 | 2016-03-21 23:32:00,1
16 | 2016-03-21 23:33:00,1
17 | 2016-03-21 23:34:00,1
18 | 2016-03-21 23:35:00,2
19 | 2016-03-21 23:36:00,1
20 | 2016-03-21 23:37:00,1
21 | 2016-03-21 23:38:00,1
22 | 2016-03-21 23:39:00,1
23 | 2016-03-21 23:40:00,1
24 | 2016-03-21 23:41:00,1
25 | 2016-03-21 23:42:00,1
26 | 2016-03-21 23:43:00,1
27 | 2016-03-21 23:44:00,1
28 | 2016-03-21 23:45:00,1
29 | 2016-03-21 23:46:00,1
30 | 2016-03-21 23:47:00,1
31 | 2016-03-21 23:48:00,1
32 | 2016-03-21 23:49:00,1
33 | 2016-03-21 23:50:00,1
34 | 2016-03-21 23:51:00,1
35 | 2016-03-21 23:52:00,1
36 | 2016-03-21 23:53:00,1
37 | 2016-03-21 23:54:00,1
38 | 2016-03-21 23:55:00,1
39 | 2016-03-21 23:56:00,1
40 | 2016-03-21 23:57:00,1
41 | 2016-03-21 23:58:00,1
42 | 2016-03-21 23:59:00,1
43 | 2016-03-22 00:00:00,1
44 | 2016-03-22 00:01:00,1
45 | 2016-03-22 00:02:00,1
46 | 2016-03-22 00:03:00,1
47 | 2016-03-22 00:04:00,1
48 | 2016-03-22 00:05:00,1
49 | 2016-03-22 00:06:00,1
50 | 2016-03-22 00:07:00,1
51 | 2016-03-22 00:08:00,1
52 | 2016-03-22 00:09:00,2
53 | 2016-03-22 00:10:00,2
54 | 2016-03-22 00:11:00,2
55 | 2016-03-22 00:12:00,2
56 | 2016-03-22 00:13:00,1
57 | 2016-03-22 00:14:00,1
58 | 2016-03-22 00:15:00,1
59 | 2016-03-22 00:16:00,1
60 | 2016-03-22 00:17:00,1
61 | 2016-03-22 00:18:00,2
62 | 2016-03-22 00:19:00,1
63 | 2016-03-22 00:20:00,1
64 | 2016-03-22 00:21:00,1
65 | 2016-03-22 00:22:00,1
66 | 2016-03-22 00:23:00,1
67 | 2016-03-22 00:24:00,1
68 | 2016-03-22 00:25:00,1
69 | 2016-03-22 00:26:00,1
70 | 2016-03-22 00:27:00,1
71 | 2016-03-22 00:28:00,2
72 | 2016-03-22 00:29:00,1
73 | 2016-03-22 00:30:00,1
74 | 2016-03-22 00:31:00,2
75 | 2016-03-22 00:32:00,1
76 | 2016-03-22 00:33:00,1
77 | 2016-03-22 00:34:00,1
78 | 2016-03-22 00:35:00,1
79 | 2016-03-22 00:36:00,1
80 | 2016-03-22 00:37:00,1
81 | 2016-03-22 00:38:00,1
82 | 2016-03-22 00:39:00,1
83 | 2016-03-22 00:40:00,1
84 | 2016-03-22 00:41:00,1
85 | 2016-03-22 00:42:00,1
86 | 2016-03-22 00:43:00,1
87 | 2016-03-22 00:44:00,1
88 | 2016-03-22 00:45:00,1
89 | 2016-03-22 00:46:00,1
90 | 2016-03-22 00:47:00,1
91 | 2016-03-22 00:48:00,1
92 | 2016-03-22 00:49:00,1
93 | 2016-03-22 00:50:00,1
94 | 2016-03-22 00:51:00,1
95 | 2016-03-22 00:52:00,1
96 | 2016-03-22 00:53:00,1
97 | 2016-03-22 00:54:00,1
98 | 2016-03-22 00:55:00,2
99 | 2016-03-22 00:56:00,1
100 | 2016-03-22 00:57:00,1
101 | 2016-03-22 00:58:00,1
102 | 2016-03-22 00:59:00,1
103 | 2016-03-22 01:00:00,1
104 | 2016-03-22 01:01:00,1
105 | 2016-03-22 01:02:00,1
106 | 2016-03-22 01:03:00,2
107 | 2016-03-22 01:04:00,1
108 | 2016-03-22 01:05:00,1
109 | 2016-03-22 01:06:00,1
110 | 2016-03-22 01:07:00,1
111 | 2016-03-22 01:08:00,1
--------------------------------------------------------------------------------
/src/util/gather_keys_oauth2.py:
--------------------------------------------------------------------------------
1 | # code from https://github.com/orcasgit/python-fitbit
2 | import cherrypy
3 | import sys
4 | import threading
5 | import traceback
6 | import webbrowser
7 |
8 | from fitbit.api import FitbitOauth2Client
9 | from oauthlib.oauth2.rfc6749.errors import MismatchingStateError, MissingTokenError
10 |
11 |
12 | class OAuth2Server:
13 | def __init__(self, client_id, client_secret,
14 | redirect_uri='http://127.0.0.1:8080/'):
15 | """ Initialize the FitbitOauth2Client """
16 | self.redirect_uri = redirect_uri
17 | self.success_html = """
18 |
You are now authorized to access the Fitbit API!
19 |
You can close this window
"""
20 | self.failure_html = """
21 | ERROR: %s
You can close this window
%s"""
22 | self.oauth = FitbitOauth2Client(client_id, client_secret)
23 |
24 | def browser_authorize(self):
25 | """
26 | Open a browser to the authorization url and spool up a CherryPy
27 | server to accept the response
28 | """
29 | url, _ = self.oauth.authorize_token_url(redirect_uri=self.redirect_uri)
30 | # Open the web browser in a new thread for command-line browser support
31 | threading.Timer(1, webbrowser.open, args=(url,)).start()
32 | cherrypy.quickstart(self)
33 |
34 | @cherrypy.expose
35 | def index(self, state, code=None, error=None):
36 | """
37 | Receive a Fitbit response containing a verification code. Use the code
38 | to fetch the access_token.
39 | """
40 | error = None
41 | if code:
42 | try:
43 | self.oauth.fetch_access_token(code, self.redirect_uri)
44 | except MissingTokenError:
45 | error = self._fmt_failure(
46 | 'Missing access token parameter.Please check that '
47 | 'you are using the correct client_secret')
48 | except MismatchingStateError:
49 | error = self._fmt_failure('CSRF Warning! Mismatching state')
50 | else:
51 | error = self._fmt_failure('Unknown error while authenticating')
52 | # Use a thread to shutdown cherrypy so we can return HTML first
53 | self._shutdown_cherrypy()
54 | return error if error else self.success_html
55 |
56 | def _fmt_failure(self, message):
57 | tb = traceback.format_tb(sys.exc_info()[2])
58 | tb_html = '%s
' % ('\n'.join(tb)) if tb else ''
59 | return self.failure_html % (message, tb_html)
60 |
61 | def _shutdown_cherrypy(self):
62 | """ Shutdown cherrypy in one second, if it's running """
63 | if cherrypy.engine.state == cherrypy.engine.states.STARTED:
64 | threading.Timer(1, cherrypy.engine.exit).start()
65 |
66 |
67 | if __name__ == '__main__':
68 |
69 | if not (len(sys.argv) == 3):
70 | print("Arguments: client_id and client_secret")
71 | sys.exit(1)
72 |
73 | server = OAuth2Server(*sys.argv[1:])
74 | server.browser_authorize()
75 |
76 | profile = server.fitbit.user_profile_get()
77 | print('You are authorized to access data for the user: {}'.format(
78 | profile['user']['fullName']))
79 |
80 | print('TOKEN\n=====\n')
81 | for key, value in server.fitbit.client.session.token.items():
82 | print('{} = {}'.format(key, value))
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fitbit Analyzer
2 | Analyzer and statistics generator for Fitbit data like sleep, steps count and heart rate. Check out also [my related introductory article](https://medium.com/@5agado/a-quest-for-better-sleep-with-fitbit-data-analysis-5f10b3f548a#.inflxkcln).
3 |
4 | Includes OAuth 2.0 **scraper** to download personal Fitbit data using the [Fitbit API Python Client Implementation](http://python-fitbit.readthedocs.io/en/latest/).
5 |
6 | ## Usage
7 | Run
8 |
9 | python setup.py install
10 |
11 | You will then be able to run the scraper via
12 |
13 | fitbit-scraper --id --secret --out dump/destination/folder/path
14 |
15 | The client-id and client-secret can be obtained by creating a Fitbit app on the official website. The scraper will than take care to download all data to the given folder, avoiding to re-download data already present (apart for the last present day, which will be re-scraped to avoid missing entries). See `fitbit-scraper --help` for a detailed usage description.
16 |
17 | ### Data Format
18 | In the *util* folder you can find some code for loading sleep, heartbeat and steps data as dumped by the scraper script.
19 | For the data-dump folder I have one folder per year, and then one sub-folder for each day, in which the different files are generated (e.g. *sleep.json*, *steps.json*).
20 |
21 | Example:
22 | ```
23 | ├───2016
24 | │ ├───2016-01-01
25 | │ │ sleep.json
26 | │ │ steps.json
27 | │ │
28 | │ └───2016-01-02
29 | │ heartbeat.json
30 | │
31 | └───2017
32 | └───2017-01-11
33 | calories.json
34 | distance.json
35 | elevation.json
36 | floors.json
37 | heartbeat.json
38 | sleep.json
39 | steps.json
40 | ```
41 |
42 | ## Analyzer and Stats
43 | Compared to basic scraping this phase has more dependencies; see the *requirements.txt* file and [here](http://conda.pydata.org/docs/using/envs.html#share-an-environment) for a guide on how to manage environments in Conda.
44 |
45 | The [Sleep Dashboard notebook](Sleep%20Dashboard.ipynb) can provide a quick way to explore your sleep data. See [related article](https://medium.com/towards-data-science/interactive-visualizations-in-jupyter-notebook-3be02ab2b8cd) and [video](https://www.youtube.com/watch?v=FYnM84TgzZU). I am currently working on notebooks for the other measures, so all feedback is welcome.
46 |
47 |
48 | ### Sleep Stats
49 | Sleep values are mapped like this: 0=none (no measure taken), 1=sleeping, 2=restless, 3=awake.
50 |
51 | * **Basic Stats** (sleep values count, sleep efficiency, hours of sleep, total minutes in bed, N max-values for each stat)
52 | * **Timing Stats** (first minute asleep, to bed time, wake up time, sleep interval min/max length)
53 | * **Intervals Stats** for each day all the sleep intervals lengths
54 | * **Intraday Stats** minute to minute report for each day, for the specified sleep values. Total value count, with normalization and centering on specific time.
55 |
56 |
57 | ### Heart Rate Stats
58 | * **Basic Stats** (count, mean, min, max, std) values for the entire dataset, or for aggregated subsets. Common aggregation features are date, year, month, day, day of week and hour
59 | * **Daily Stats** plotting of average heart-rate by day, month, year, weekday
60 | * **Min/Max Values** get the N min or max heartbeat values in the dataset
61 |
62 | ### Steps Stats
63 | * **Daily Count** steps count per day
64 |
65 | **NEW**
66 | ### Combined Stats
67 | * **Correlation** correlation coefficient between heterogeneous stats (e.g. steps and sleep)
68 | * **Combined TOGGL and heart-rate stats** explore relations between Toggl activities and projects and heart-rate
69 |
70 | ## TODO
71 | * Toggl data exploratory notebook
72 | * moving average sleep (days for achieving flatness)
73 | * correlation between constant exercise/running and decreased heart-rate
74 | * formal verification of causality (e.g. good sleep causes to walk, or walk cause good sleep)
75 | * derive sleep-states (REM/NREM) stats (e.g. identify, how long in each) from basic ones, or via correlation with heartbeat (as for now doesn't seem to be feasible actual ECG is still the more proper option)
76 | * max HR and zones (% of MHR)
77 |
78 | ## License
79 |
80 | Released under version 2.0 of the [Apache License].
81 |
82 | [Apache license]: http://www.apache.org/licenses/LICENSE-2.0
83 | [my related article]: https://medium.com/@5agado/conversation-analyzer-baa80c566d7b#.w20u1gltf
--------------------------------------------------------------------------------
/src/util/scraper.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import json
3 | import sys
4 | import argparse
5 | from pathlib import Path
6 |
7 | import fitbit
8 | from src.util import logger
9 | from src.util import gather_keys_oauth2 as Oauth2
10 |
11 |
12 | def dumpToFile(data_type, dumpDir: Path, date, data):
13 | directory = dumpDir / str(date.year) / str(date)
14 | directory.mkdir(parents=True, exist_ok=True)
15 | with (directory / "{}.json".format(data_type)).open(mode='w') as f:
16 | f.write(json.dumps(data, indent=True))
17 |
18 |
19 | def previouslyDumped(dumpDir: Path, date):
20 | return (dumpDir / str(date.year) / str(date)).is_dir()
21 |
22 |
23 | def dumpDay(client, dumpDir: Path, date):
24 | steps_data = client.intraday_time_series('activities/steps', date)
25 | intradayData = steps_data['activities-steps-intraday']['dataset']
26 | if not intradayData:
27 | logger.info("No {} measures for {}. Skipping the rest too".format('steps', date.split('\\')[-1]))
28 | return None
29 |
30 | dumpToFile("steps", dumpDir, date, steps_data)
31 | dumpToFile("sleep", dumpDir, date, client.get_sleep(date))
32 | dumpToFile("calories", dumpDir, date, client.intraday_time_series('activities/calories', date))
33 | dumpToFile("distance", dumpDir, date, client.intraday_time_series('activities/distance', date))
34 | dumpToFile("floors", dumpDir, date, client.intraday_time_series('activities/floors', date))
35 | dumpToFile("elevation", dumpDir, date, client.intraday_time_series('activities/elevation', date))
36 | dumpToFile("heartbeat", dumpDir, date, client.intraday_time_series('activities/heart', date))
37 |
38 |
39 | def scrapeFromDateOnward(startDate, dumpDir: Path, client):
40 | date = datetime.datetime.strptime(startDate, "%Y-%m-%d").date()
41 | todayDate = datetime.date.today()
42 | while previouslyDumped(dumpDir, date):
43 | logger.info("Already scraped {}".format(datetime.datetime.strftime(date, "%Y-%m-%d")))
44 | date += datetime.timedelta(days=1)
45 |
46 | date -= datetime.timedelta(days=1)
47 | logger.info("Will RE-Scrape data for {}".format(datetime.datetime.strftime(date, "%Y-%m-%d")))
48 | while date < todayDate:
49 | logger.info("Scraping data for {}".format(datetime.datetime.strftime(date, "%Y-%m-%d")))
50 | dumpDay(client, dumpDir, date)
51 | date += datetime.timedelta(days=1)
52 |
53 |
54 | def scrapeFromTodayAndBackward(dumpDir: Path, client, limit, stop_if_already_dumped=True):
55 | # dumping
56 | count = 1
57 | date = datetime.date.today()
58 | while count < limit:
59 | if previouslyDumped(dumpDir, date):
60 | logger.info("Already scraped {}".format(date.isoformat()))
61 | if stop_if_already_dumped:
62 | print("Stopping the scraping")
63 | break
64 | date -= datetime.timedelta(days=1)
65 | continue
66 | logger.info("Scraping data for {}".format(date.isoformat()))
67 | dumpDay(client, dumpDir, date)
68 | date -= datetime.timedelta(days=1)
69 | count += 1
70 | dumpDay(client, dumpDir, date)
71 |
72 |
73 | def main(_=None):
74 | parser = argparse.ArgumentParser(description='Fitbit Scraper')
75 | parser.add_argument('--id', metavar='clientId', dest='clientId', required=True,
76 | help="client-id of your Fitbit app")
77 | parser.add_argument('--secret', metavar='clientSecret', dest='clientSecret', required=True,
78 | help="client-secret of your Fitbit app")
79 | parser.add_argument('--out', metavar='outDir', dest='outDir', required=True,
80 | help="output data destination folder")
81 | parser.add_argument('--start', dest='startDate', default='2016-01-01',
82 | help="Date from which to start the forward scraping. Defaults to 2016-01-01")
83 | #parser.add_argument('--limit', type=int, dest='limit', default=400,
84 | # help="maximum number of days to scrape")
85 |
86 | args = parser.parse_args()
87 | clientId = args.clientId
88 | clientSecret = args.clientSecret
89 | dumpDir = Path(args.outDir)
90 | startDate = args.startDate
91 | #limit = args.limit
92 |
93 | server = Oauth2.OAuth2Server(clientId, clientSecret)
94 | server.browser_authorize()
95 |
96 | ACCESS_TOKEN = server.oauth.session.token['access_token']
97 | REFRESH_TOKEN = server.oauth.session.token['refresh_token']
98 |
99 | client = fitbit.Fitbit(clientId, clientSecret, oauth2=True,
100 | access_token=ACCESS_TOKEN, refresh_token=REFRESH_TOKEN)
101 |
102 | scrapeFromDateOnward(startDate, dumpDir, client)
103 |
104 |
105 | if __name__ == "__main__":
106 | main(sys.argv[1:])
--------------------------------------------------------------------------------
/heartBeatTesting.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": null,
6 | "metadata": {
7 | "ExecuteTime": {
8 | "end_time": "2017-10-05T09:30:32.985827",
9 | "start_time": "2017-10-05T09:30:31.186724"
10 | }
11 | },
12 | "outputs": [],
13 | "source": [
14 | "import pandas as pd\n",
15 | "import numpy as np\n",
16 | "import seaborn as sns\n",
17 | "import datetime\n",
18 | "\n",
19 | "from pathlib import Path\n",
20 | "import os\n",
21 | "import sys\n",
22 | "sys.path.append(os.path.join(os.getcwd(), \"src\"))\n",
23 | "\n",
24 | "from util import utils\n",
25 | "from util import plotting\n",
26 | "from stats import hbStats\n",
27 | "\n",
28 | "import time\n",
29 | "\n",
30 | "%load_ext autoreload\n",
31 | "%autoreload 2"
32 | ]
33 | },
34 | {
35 | "cell_type": "markdown",
36 | "metadata": {},
37 | "source": [
38 | "### Load and prepare data"
39 | ]
40 | },
41 | {
42 | "cell_type": "code",
43 | "execution_count": null,
44 | "metadata": {},
45 | "outputs": [],
46 | "source": [
47 | "dataFolder = str(Path.home() / \"Documents/scripts_data/fitbit_analyzer/dataDump/\")"
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": null,
53 | "metadata": {
54 | "scrolled": true
55 | },
56 | "outputs": [],
57 | "source": [
58 | "start = time.time()\n",
59 | "hbData = utils.loadHBData(dataFolder)\n",
60 | "end = time.time()\n",
61 | "print(\"Data loaded in {:.2f}s\".format(end - start))\n",
62 | "data = pd.concat(hbData, ignore_index=True)\n",
63 | "print(\"Sample\")\n",
64 | "print(data.head())"
65 | ]
66 | },
67 | {
68 | "cell_type": "code",
69 | "execution_count": null,
70 | "metadata": {},
71 | "outputs": [],
72 | "source": [
73 | "data.set_index('datetime', inplace=True)"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": null,
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "data[-2:]"
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "metadata": {},
88 | "source": [
89 | "## Plot"
90 | ]
91 | },
92 | {
93 | "cell_type": "code",
94 | "execution_count": null,
95 | "metadata": {},
96 | "outputs": [],
97 | "source": [
98 | "DAYS = 7\n",
99 | "WEEKS = 5\n",
100 | "NB_VALUES = 60*24 # minutes per day\n",
101 | "assert len(test_data)==DAYS*WEEKS*NB_VALUES"
102 | ]
103 | },
104 | {
105 | "cell_type": "code",
106 | "execution_count": null,
107 | "metadata": {},
108 | "outputs": [],
109 | "source": [
110 | "min_val = 0\n",
111 | "max_val = 1\n",
112 | "test_data['value'] = min_val + (((test_data['value'] - test_data['value'].min()) * (max_val - min_val)) / (\n",
113 | " test_data['value'].max() - test_data['value'].min()))\n",
114 | "print(len(test_data))"
115 | ]
116 | },
117 | {
118 | "cell_type": "code",
119 | "execution_count": null,
120 | "metadata": {},
121 | "outputs": [],
122 | "source": [
123 | "%matplotlib notebook\n",
124 | "from matplotlib import pyplot as plt\n",
125 | "from matplotlib import animation\n",
126 | "\n",
127 | "fig, ax = plt.subplots(dpi=100, figsize=(5, 4))\n",
128 | "im = ax.imshow(grid, cmap='gray')\n",
129 | "#plt.axis('off')\n",
130 | "\n",
131 | "def animate(i, test_data, im, WEEKS, DAYS, NB_VALUES):\n",
132 | " grid = []\n",
133 | " for k in range(WEEKS):\n",
134 | " week = []\n",
135 | " for j in range(DAYS):\n",
136 | " week.append(test_data.loc[i+((DAYS*k+j)*NB_VALUES)].value)\n",
137 | " grid.append(week)\n",
138 | " im.set_data(np.array(grid))\n",
139 | "\n",
140 | "ani = animation.FuncAnimation(fig, animate, frames=10000, interval=10,\n",
141 | " fargs=[test_data, im, WEEKS, DAYS, NB_VALUES]) # be sure to pass the additional args needed for the animation\n",
142 | " #.save('anim.mp4', writer=animation.FFMpegFileWriter(fps=30))"
143 | ]
144 | },
145 | {
146 | "cell_type": "code",
147 | "execution_count": null,
148 | "metadata": {},
149 | "outputs": [],
150 | "source": [
151 | "for i in range(50):\n",
152 | " grid = []\n",
153 | " for k in range(WEEKS):\n",
154 | " week = []\n",
155 | " for j in range(DAYS):\n",
156 | " week.append(test_data.loc[i+((DAYS*k+j)*NB_VALUES)].value)\n",
157 | " grid.append(week)\n",
158 | " im.set_data(np.array(grid))\n",
159 | "print(np.array(grid).shape)"
160 | ]
161 | },
162 | {
163 | "cell_type": "code",
164 | "execution_count": null,
165 | "metadata": {},
166 | "outputs": [],
167 | "source": [
168 | "np.array(grid)"
169 | ]
170 | },
171 | {
172 | "cell_type": "code",
173 | "execution_count": null,
174 | "metadata": {},
175 | "outputs": [],
176 | "source": [
177 | "fig, ax = plt.subplots(dpi=100, figsize=(5, 4))\n",
178 | "im = ax.imshow(np.array(grid), cmap='gray')"
179 | ]
180 | },
181 | {
182 | "cell_type": "markdown",
183 | "metadata": {},
184 | "source": [
185 | "### Basic Overall Stats"
186 | ]
187 | },
188 | {
189 | "cell_type": "code",
190 | "execution_count": null,
191 | "metadata": {},
192 | "outputs": [],
193 | "source": [
194 | "hbStats.basicStats(data)"
195 | ]
196 | },
197 | {
198 | "cell_type": "markdown",
199 | "metadata": {},
200 | "source": [
201 | "### Basic Aggregated Stats"
202 | ]
203 | },
204 | {
205 | "cell_type": "code",
206 | "execution_count": null,
207 | "metadata": {},
208 | "outputs": [],
209 | "source": [
210 | "#hourStats = hbStats.groupByBasicStats(data['datetime'].dt.hour, data, 'hour')\n",
211 | "#plotting.plot(hourStats, ['count', 'max', 'min', 'std'], 'hour', 2, 2)\n",
212 | "dayStats = hbStats.groupByBasicStats(data['datetime'].dt.day, data, 'day')\n",
213 | "plotting.plot(dayStats, ['count', 'max', 'min', 'std'], 'day', 2, 2)\n",
214 | "#monthStats = hbStats.groupByBasicStats(data['datetime'].dt.month, data, 'month')\n",
215 | "#plotting.plotMonthlyStatsHb(monthStats)\n",
216 | "#dayOfWeekStats = hbStats.groupByBasicStats(data['datetime'].dt.dayofweek, data, 'weekday')\n",
217 | "#plotting.plotWeekdayStatsHb(dayOfWeekStats)\n",
218 | "#plotting.plotDailyStatsHb(data)"
219 | ]
220 | },
221 | {
222 | "cell_type": "code",
223 | "execution_count": null,
224 | "metadata": {},
225 | "outputs": [],
226 | "source": [
227 | "dayStats.head()"
228 | ]
229 | },
230 | {
231 | "cell_type": "markdown",
232 | "metadata": {},
233 | "source": [
234 | "### Min/Max Values"
235 | ]
236 | },
237 | {
238 | "cell_type": "code",
239 | "execution_count": null,
240 | "metadata": {},
241 | "outputs": [],
242 | "source": [
243 | "hbStats.getMaxValues(data, 5)"
244 | ]
245 | },
246 | {
247 | "cell_type": "code",
248 | "execution_count": null,
249 | "metadata": {},
250 | "outputs": [],
251 | "source": [
252 | "hbStats.getMinValues(data, 5)"
253 | ]
254 | },
255 | {
256 | "cell_type": "code",
257 | "execution_count": null,
258 | "metadata": {},
259 | "outputs": [],
260 | "source": []
261 | }
262 | ],
263 | "metadata": {
264 | "anaconda-cloud": {},
265 | "kernelspec": {
266 | "display_name": "Python [conda env:Anaconda3]",
267 | "language": "python",
268 | "name": "conda-env-Anaconda3-py"
269 | },
270 | "language_info": {
271 | "codemirror_mode": {
272 | "name": "ipython",
273 | "version": 3
274 | },
275 | "file_extension": ".py",
276 | "mimetype": "text/x-python",
277 | "name": "python",
278 | "nbconvert_exporter": "python",
279 | "pygments_lexer": "ipython3",
280 | "version": "3.6.3"
281 | },
282 | "toc": {
283 | "base_numbering": 1,
284 | "nav_menu": {
285 | "height": "104px",
286 | "width": "252px"
287 | },
288 | "number_sections": true,
289 | "sideBar": true,
290 | "skip_h1_title": false,
291 | "title_cell": "Table of Contents",
292 | "title_sidebar": "Contents",
293 | "toc_cell": false,
294 | "toc_position": {},
295 | "toc_section_display": "block",
296 | "toc_window_display": false
297 | }
298 | },
299 | "nbformat": 4,
300 | "nbformat_minor": 1
301 | }
302 |
--------------------------------------------------------------------------------
/src/stats/sleepStats.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import numpy as np
3 | from enum import Enum
4 | from datetime import time
5 |
6 | class SleepValue(Enum):
7 | none = 0
8 | sleeping = 1
9 | restless = 2
10 | awake = 3
11 |
12 | SLEEP_VALUES = {x.value:x.name for x in SleepValue}
13 |
14 | STATS_NAME_BASIC = 'basicStats'
15 | STATS_NAME_TIMING = 'timingStats'
16 | STATS_NAME_BASIC_AND_TIMING = 'basicAndTimingStats'
17 | STATS_NAME_INTERVALS = 'intervalsStats'
18 | STATS_NAME_INTRADAY = 'intradayStats'
19 |
20 | def generateStatsFrom(data, statsType, **kwargs):
21 | """
22 | Generic method for stats generation.
23 | :param data: sleep data. List of dataframes, one for each day
24 | :param statsType: name of the stat to be generated
25 | :return:
26 | """
27 |
28 | if statsType == STATS_NAME_BASIC:
29 | stats = generateBasicStats(data)
30 | elif statsType == STATS_NAME_TIMING:
31 | stats = generateTimingStats(data, **kwargs)
32 | elif statsType == STATS_NAME_BASIC_AND_TIMING:
33 | basicStats = generateBasicStats(data)
34 | timingStats = generateTimingStats(data, **kwargs)
35 | stats = pd.concat([basicStats, timingStats], axis=1)
36 | elif statsType == STATS_NAME_INTERVALS:
37 | stats = generateIntervalsStats(data, **kwargs)
38 | elif statsType == STATS_NAME_INTRADAY:
39 | stats = generateIntradayStats(data, **kwargs)
40 | else:
41 | raise Exception(statsType + ' Stat not implemented')
42 | return stats
43 |
44 | #Each row is a day with basic stats like sleep values count, efficiency and total minutes
45 | def generateBasicStats(filesData):
46 | #Create new df to store the stats
47 | basicStats = pd.DataFrame(columns=[1,2,3,'total_minutes','sleep_efficiency'])
48 | basicStats.index.rename('date', inplace=True)
49 |
50 | #For each file-data, extract the basic stats, and add them to the df
51 | for fileData in filesData:
52 | stats = fileData.groupby(['value'], as_index=False).size()
53 | stats['total_minutes'] = stats.sum()
54 | date = getSleepDate(fileData)
55 | basicStats.loc[date] = stats
56 |
57 | #Derive additional stats
58 | basicStats = basicStats.fillna(0).astype(int)
59 | basicStats['sleep_efficiency'] = (basicStats[SleepValue.sleeping.value]/basicStats['total_minutes'])*100
60 | basicStats['sleep_inefficiency'] = (basicStats['sleep_efficiency']-100).abs()
61 | basicStats['sleep_hours'] = (basicStats[SleepValue.sleeping.value]/60)
62 | basicStats['total_hours'] = basicStats['total_minutes']/60
63 |
64 | #Rename columns
65 | columns = SLEEP_VALUES
66 | columns['value'] = 'date'
67 | basicStats.rename(columns=columns, inplace=True)
68 | return basicStats
69 |
70 | #Each row is a day with timing stats about like first minute asleep, interval avg and max length
71 | def generateTimingStats(filesData, minSleepIntervalRequired=0):
72 | #Create new df to store the stats
73 | intervalsStats = pd.DataFrame(columns=['first_min_asleep',
74 | 'to_bed_time',
75 | 'wake_up_time',
76 | 'sleep_interval_max_len',
77 | 'sleep_interval_avg_len'])
78 | intervalsStats.index.rename('date', inplace=True)
79 |
80 | #For each file-data, extract the basic stats, and add them to the df
81 | for fileData in filesData:
82 | firstMinuteAsleep = getFirstMinuteAsleep(fileData, minSleepIntervalRequired)
83 | stats = getSleepIntervalsStats(fileData, minSleepIntervalRequired)
84 | date = getSleepDate(fileData)
85 | intervalsStats.loc[date] = [firstMinuteAsleep, fileData.iloc[0]['datetime'].time(),
86 | fileData.iloc[-1]['datetime'].time(),
87 | stats.len.max(), stats.len.mean()]
88 |
89 | return intervalsStats
90 |
91 | #Each row is a day with length of all its sleep intervals
92 | def generateIntervalsStats(filesData, minSleepIntervalRequired=0):
93 | #Create new df to store the stats
94 | intervalsStats = pd.DataFrame(columns=np.arange(20))
95 |
96 | #For each file-data, extract the basic stats, and add them to the df
97 | for fileData in filesData:
98 | stats = getSleepIntervalsStats(fileData, minSleepIntervalRequired)
99 | date = getSleepDate(fileData)
100 | intervalsStats.loc[date] = stats.len
101 |
102 | return intervalsStats
103 |
104 | #Each row is a complete intraday value array for the corresponding day
105 | def generateIntradayStats(filesData, useTime=True):
106 | #Create new df to store the stats
107 | if useTime:
108 | minutes = pd.date_range('00:00', '23:59', freq='1min')
109 | intradayStats = pd.DataFrame(columns=[x.time().strftime("%H:%M")
110 | for x in minutes])
111 | else:
112 | intradayStats = pd.DataFrame(columns=np.arange(600))
113 | intradayStats.index.rename('date', inplace=True)
114 |
115 | #For each file-data, extract the basic stats, and add them to the df
116 | for fileData in filesData:
117 | data = fileData.copy(deep=True)
118 | date = getSleepDate(data)
119 | data['time'] = [x.strftime("%H:%M") for x in data['datetime'].dt.time]
120 | data.set_index(['time'], inplace=True)
121 | intradayStats.loc[date] = data['value']
122 |
123 | return intradayStats
124 |
125 | #----------------------------#
126 | # SECOND LEVEL METHODS #
127 | #----------------------------#
128 |
129 | def normalizedIntradayCountStats(intradayStats, limitCount=5):
130 | # For each minute, number of days for which we have a valid measure (record)
131 | notNullCount = intradayStats.count()
132 | # Ignore minutes where we have low level of records
133 | notNullCount[notNullCount < limitCount] = None
134 | # Count how many times each value appears for each minute
135 | valueCount = intradayStats.apply(pd.value_counts)
136 | # Normalize each minute by records count
137 | res = valueCount.div(notNullCount, axis=1)
138 | return res
139 |
140 | def centerIntradayCountStats(intradayCountStats, timeToCenterOn="12:00"):
141 | columns = list(intradayCountStats.columns.values)
142 | k = columns.index(timeToCenterOn)
143 | columns = columns[k:] + columns[:k]
144 | return intradayCountStats[columns]
145 |
146 | def getSleepIntervalsStats(data, minSleepIntervalRequired=0):
147 | sleepIntervals = getSleepIntervals(data, minSleepIntervalRequired)
148 |
149 | #Create new df to store the stats
150 | stats = pd.DataFrame(columns=['minute_start','minute_end','time_start','time_end','len'])
151 |
152 | #For each interval get start, end, len stats, and add it to the df
153 | for i, interval in enumerate(sleepIntervals):
154 | #Substract one because interval indicated the minute, and starts from 1
155 | timeStart = data.iloc[interval[0]-1]['datetime']
156 | timeEnd = data.iloc[interval[1]-1]['datetime']
157 | stats.loc[i] = [interval[0], interval[1], timeStart, timeEnd, (interval[1] - interval[0])+1]
158 |
159 | return stats
160 |
161 | #Return the date associated with the provides sleep data.
162 | #It should be the date associated with the first recorded minute, but if this is after
163 | #midnight and before noon, we want to consider the previous day.
164 | def getSleepDate(data):
165 | firstDatetime = data.ix[0]['datetime']
166 | if time(12,00) <= firstDatetime.time()<=time(23,59):
167 | return firstDatetime.date()
168 | else:
169 | return (firstDatetime - pd.DateOffset(1)).date()
170 |
171 |
172 | def getFirstMinuteAsleep(data, minSleepIntervalRequired=0):
173 | firstAsleep = None
174 |
175 | values = data['value']
176 | indxs = values[values==SleepValue.sleeping.value].index
177 | for indx in indxs:
178 | if (len(values[indx:indx+minSleepIntervalRequired]) >= minSleepIntervalRequired and
179 | (values[indx:indx+minSleepIntervalRequired]==SleepValue.sleeping.value).all()):
180 | #Indexing starts from 0, minutes start from 1
181 | firstAsleep = indx+1
182 | break
183 |
184 | return firstAsleep
185 |
186 | def getSleepIntervals(data, minSleepIntervalRequired=0):
187 | values = data['value']
188 | splits = np.append(np.where(np.diff(values)!=0)[0], [len(values)-1])+1
189 |
190 | prev = 0
191 | intervals = []
192 | for split in splits:
193 | if values[prev] == SleepValue.sleeping.value and (split-prev)>=minSleepIntervalRequired:
194 | intervals.append((prev+1, split))
195 | prev = split
196 |
197 | return intervals
198 |
199 | def getMaxValues(basicStats, n, statName):
200 | return basicStats.nlargest(n, statName)[statName]
201 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/src/util/utils.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import json
3 | import os
4 | import sys
5 | import datetime
6 | from datetime import time
7 |
8 | from src.util import logger
9 |
10 | def loadIntradayData(filepath):
11 | data = pd.read_csv(filepath, parse_dates=[0], names=['datetime', 'value'])
12 | return data
13 |
14 | #TODO implement and test loading more than one intraday interval
15 | def loadSleepData(dumpDir):
16 | """
17 | Load sleep data from dumping done using the official Fitbit API.
18 | Check README file for further info on the scraping process and saved format
19 | :param dumpDir: the folder where the date has been dumped
20 | :return: a list of dataframes, one for each day, containing the intraday sleep data
21 | """
22 | SLEEP_VALUE_NONE = 0
23 | def loadFun(jsonData):
24 | sleeps = jsonData['sleep']
25 | if not sleeps:
26 | return None
27 | date = datetime.datetime.strptime(sleeps[0]['dateOfSleep'], "%Y-%m-%d").date()
28 | if len(sleeps)>1:
29 | logger.info("There are {} sleep records for {}, taking main sleep".format(len(sleeps), date))
30 | intradayData = None
31 | for sleep in sleeps:
32 | if sleep['isMainSleep']:
33 | intradayData = sleep['minuteData']
34 | break
35 | dayToSubstract = datetime.timedelta(days=1)
36 | df = pd.read_json(json.dumps(intradayData), convert_dates=['time'])
37 | if (df['value']==SLEEP_VALUE_NONE).all():
38 | logger.info("There are only none values for {}".format(date))
39 | return None
40 | df['datetime'] = df.apply(lambda x: datetime.datetime.combine(
41 | date - dayToSubstract if time(12,00) <= x['dateTime'].time() <=time(23,59) else date,
42 | x['dateTime'].time()), axis=1)
43 | df.drop('dateTime', inplace=True, axis=1)
44 | return df
45 |
46 | return _loadData(dumpDir, 'sleep', loadFun)
47 |
48 | def loadHBData(dumpDir, concat=False):
49 | """
50 | Load heart-rate data from dumping done using the official Fitbit API.
51 | Check README file for further info on the scraping process and saved format
52 | :param concat: specify when to concat all entries in a single dataframe
53 | :param dumpDir: the folder where the date has been dumped
54 | :return: a list of dataframes, one for each day, containing the intraday heart-rate data.
55 | or a single one, if concat is set to True.
56 | """
57 | def loadFun(jsonData):
58 | summaryData = jsonData['activities-heart']
59 | date = summaryData[0]['dateTime']
60 | if len(summaryData)!=1:
61 | logger.info("There are {} heart data entries for {}".format(len(summaryData), date))
62 | intradayData = jsonData['activities-heart-intraday']['dataset']
63 | if not intradayData:
64 | return None
65 | df = pd.read_json(json.dumps(intradayData))
66 | df['datetime'] = pd.to_datetime(date + ' ' + df['time'])
67 | df.drop('time', inplace=True, axis=1)
68 | return df
69 |
70 | if concat:
71 | return pd.concat(_loadData(dumpDir, 'heartbeat', loadFun), ignore_index=True)
72 | else:
73 | return _loadData(dumpDir, 'heartbeat', loadFun)
74 |
75 | # TODO load heartRateZones data
76 | def loadHBSummaryData(dumpDir):
77 | """
78 | Load heart-rate summary data from dumping done using the official Fitbit API.
79 | Check README file for further info on the scraping process and saved format
80 | :param dumpDir: the folder where the date has been dumped
81 | :return: a list of tuples, one for each day, containing summary heart-rate info
82 | """
83 | def loadFun(jsonData):
84 | summaryData = jsonData['activities-heart']
85 | date = datetime.datetime.strptime(summaryData[0]['dateTime'], "%Y-%m-%d").date()
86 | if len(summaryData)!=1:
87 | logger.info("There are {} heart data entries for {}".format(len(summaryData), date))
88 |
89 | try:
90 | restingHeartRate = summaryData[0]['value']['restingHeartRate']
91 | except KeyError:
92 | logger.info("No resting heart rate info for {}".format(date))
93 | return None
94 | return date, restingHeartRate
95 |
96 | entries = _loadData(dumpDir, 'heartbeat', loadFun)
97 | return pd.DataFrame(entries, columns=['date', 'rhr']).set_index(['date'])
98 |
99 | def loadStepsData(dumpDir):
100 | """
101 | Load steps data from dumping done using the official Fitbit API.
102 | Check README file for further info on the scraping process and saved format
103 | :param dumpDir: the folder where the date has been dumped
104 | :return: a list of dataframes, one for each day, containing the intraday steps data
105 | """
106 | def loadFun(jsonData):
107 | intradayData = jsonData['activities-steps-intraday']['dataset']
108 | date = jsonData['activities-steps'][0]['dateTime']
109 | if not intradayData:
110 | return None
111 | df = pd.read_json(json.dumps(intradayData))
112 | df['datetime'] = pd.to_datetime(date + ' ' + df['time'])
113 | df.drop('time', inplace=True, axis=1)
114 | return df
115 |
116 | return _loadData(dumpDir, 'steps', loadFun)
117 |
118 | def loadTotalSteps(dumpDir):
119 | """
120 | Load total steps count from dumping done using the official Fitbit API.
121 | Check README file for further info on the scraping process and saved format
122 | :param dumpDir: the folder where the date has been dumped
123 | :return: a dataframe containing the total steps count indexed by day
124 | """
125 | def loadFun(jsonData):
126 | data = jsonData['activities-steps']
127 | date = datetime.datetime.strptime(data[0]['dateTime'],
128 | "%Y-%m-%d").date()
129 | if len(data)!=1:
130 | logger.info("There are {} steps data entries for {}".format(len(data), date))
131 | totalSteps = int(data[0]['value'])
132 | return date, totalSteps
133 |
134 | entries = _loadData(dumpDir, 'steps', loadFun)
135 | return pd.DataFrame(entries, columns=['date', 'steps']).set_index(['date'])
136 |
137 | #TODO maybe better to return a dictionary? Or for some types it will not work
138 | def _loadData(dumpDir, dataType, loadFun):
139 | """
140 | Helper method.
141 | For the data-dump folder there should be one folder per year, and then one sub-folder for each day,
142 | in which the different files are generated (e.g. sleep.json, steps.json)
143 | :param dumpDir: the folder where the date has been dumped
144 | :param dataType: the type of data to be loaded, equivalent to the name of the corresponding file
145 | :param loadFun: function defining the procedure for the data loading
146 | :return: a list of objects as defined in loadFun
147 | """
148 | data = []
149 | # First level should be the year
150 | yearDirs = getAllSubDirsNamesOf(dumpDir)
151 | # Second level should be the date
152 | for year in yearDirs:
153 | dates = getAllSubDirsNamesOf(year)
154 | for date in dates:
155 | # Dumped files are named .json
156 | filename = os.path.join(date, dataType) + '.json'
157 | try:
158 | with open(filename) as fileData:
159 | jsonData = json.load(fileData)
160 | dayData = loadFun(jsonData)
161 | if dayData is None:
162 | logger.info("No {} measures for {}".format(dataType, date.split('\\')[-1]))
163 | continue
164 | else:
165 | data.append(dayData)
166 | except FileNotFoundError:
167 | logger.warning("{} not found. Might be cause last scraped day.".format(filename))
168 |
169 | return data
170 |
171 | def loadTogglData(reportsPaths, columnsToDrop=None):
172 | """
173 | Load a list of toggl reports in a dataframe
174 | :param reportsPaths: list of reports paths
175 | :param columnsToDrop: list of names of columns to drop, all need to be present
176 | :return: all reports unified in a single dataframe
177 | """
178 | reports = []
179 | # for each path, load and add report to list
180 | for path in reportsPaths:
181 | # lovely automatic parsing of dates as well as combination of date and time
182 | report = pd.read_csv(path, parse_dates=[['Start date', 'Start time'],
183 | ['End date', 'End time']])
184 | # rename datetime columns
185 | report = report.rename(index=str, columns={"Start date_Start time": "Start",
186 | "End date_End time": "End"})
187 | # drop unnecessary fields
188 | if columnsToDrop:
189 | report.drop(columnsToDrop, axis=1, inplace=True)
190 | reports.append(report)
191 | # concatenate reports in single dataframe
192 | return pd.concat(reports, ignore_index=True)
193 |
194 | def getAllSubDirsNamesOf(mainDir):
195 | subDirs = filter(os.path.isdir,
196 | [os.path.join(mainDir, subDir) for subDir in os.listdir(mainDir)])
197 | return subDirs
--------------------------------------------------------------------------------
/src/util/plotting.py:
--------------------------------------------------------------------------------
1 | import matplotlib.pyplot as plt
2 | import numpy as np
3 | import pandas as pd
4 | import seaborn as sns
5 | from scipy import stats as scipystats
6 |
7 | from src.stats import sleepStats, hbStats
8 |
9 | NAMES={'sleep_inefficiency':'Sleep Inefficiency (%)',
10 | 'sleep_efficiency':'Sleep Efficiency (%)',
11 | 'restless':'Restless (minutes)',
12 | 'awake':'Awake (minutes)',
13 | 'total_minutes':'Total Minutes',
14 | 'sleep_hours':'Hours of Sleep',
15 | 'first_min_asleep':'First Minute Asleep'}
16 |
17 | dayOfWeek = {0: 'Mon', 1: 'Tue', 2: 'Wed', 3: 'Thur', 4: 'Fri', 5: 'Sat', 6: 'Sun'}
18 | dayOfWeekOrder = ['Mon', 'Tue', 'Wed', 'Thur', 'Fri', 'Sat', 'Sun']
19 |
20 | months={1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul', 8:'Aug',
21 | 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
22 | monthsOrder = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
23 |
24 | def plotPreliminaryStats(stats):
25 | """
26 | Plot measures distribution using histograms
27 | :param stats: data to plot
28 | """
29 | columns = ['sleep_efficiency', 'restless', 'awake', 'total_minutes', 'sleep_hours',
30 | 'first_min_asleep']
31 | plotStats = stats[columns]
32 | plotStats = plotStats.rename(columns=NAMES)
33 | return plotStats.hist()
34 | #sns.plt.show()
35 |
36 | def plotWeekdayStatsSleep(stats):
37 | columns = ['sleep_efficiency', 'restless', 'sleep_hours',
38 | 'first_min_asleep', 'awake', 'total_minutes']
39 | return _plotWeekdayStats(stats, columns, groupBy=True)
40 |
41 | def plotWeekdayStatsHb(stats):
42 | columns = ['count', 'mean', 'std']
43 | return _plotWeekdayStats(stats, columns, groupBy=False)
44 |
45 | def _plotWeekdayStats(stats, columns, groupBy=True):
46 | dataToPlot = stats.copy()
47 | # Group by weekday and rename date column
48 | if groupBy:
49 | dataToPlot = dataToPlot.groupby(stats['date'].dt.weekday).mean()
50 | dataToPlot = dataToPlot.reset_index().rename(columns={'date':'weekday'})
51 |
52 | # change stats from columns to row attribute
53 | dataToPlot = pd.melt(dataToPlot, id_vars=['weekday'], value_vars=columns,
54 | var_name='stats', value_name='val')
55 | # Rename stats and weekdays
56 | dataToPlot['stats'].replace(NAMES, inplace=True)
57 | dataToPlot['weekday'].replace(dayOfWeek, inplace=True)
58 | # Plot
59 | g = sns.factorplot(data=dataToPlot, x="weekday", y="val", col="stats",
60 | order=dayOfWeekOrder, kind="point", sharey=False, col_wrap=3)
61 | g.set_xticklabels(rotation=45)
62 | g.set(xlabel='')
63 | return g
64 | #sns.plt.show()
65 |
66 | def plotWeekdayStatsByMonthSleep(stats):
67 | stat_name = 'sleep_efficiency'
68 | return _plotWeekdayByMonthStats(stats, stat_name)
69 |
70 | def _plotWeekdayByMonthStats(stats, stat_name):
71 | dataToPlot = _prepareWeekdayByMonthStats(stats)
72 |
73 | # Plot
74 | g = sns.pointplot(data=dataToPlot, x="day", y=stat_name, hue="month", order=dayOfWeekOrder)
75 | g.set(xlabel='')
76 | g.set_ylabel(NAMES[stat_name])
77 | return g
78 | #sns.plt.show()
79 |
80 | def _prepareWeekdayByMonthStats(stats):
81 | # Add day and month columns, and groupby
82 | stats = stats.copy()
83 | stats['day'] = stats['date'].dt.weekday
84 | stats['month'] = stats['date'].dt.month
85 | dataToPlot = stats.groupby(['day', 'month']).mean()
86 |
87 | dataToPlot = dataToPlot.reset_index()
88 | dataToPlot['day'].replace(dayOfWeek, inplace=True)
89 | dataToPlot['month'].replace(months, inplace=True)
90 |
91 | return dataToPlot
92 |
93 | # def plotWeekdayStats(stats, columns):
94 | # """
95 | # Plot aggregated (mean) stats by dayOfWeek
96 | # :param stats: data to plot
97 | # :param columns: columns from stats to plot
98 | # """
99 | # MEASURE_NAME = 'weekday'
100 | # dayOfWeek={0:'Mon', 1:'Tue', 2:'Wed', 3:'Thur', 4:'Fri', 5:'Sat', 6:'Sun'}
101 | # order = ['Mon','Tue','Wed','Thur','Fri','Sat','Sun']
102 | # stats[MEASURE_NAME] = stats[MEASURE_NAME].map(dayOfWeek)
103 | #
104 | # f, axes = getAxes(2,2)
105 | # for i, c in enumerate(columns):
106 | # if c in NAMES:
107 | # c = NAMES[c]
108 | # g = sns.barplot(x=MEASURE_NAME, y=c, data=stats, order=order, ax=axes[i])
109 | # g.set_xlabel('')
110 | # sns.plt.show()
111 | # #plot(stats, columns, MEASURE_NAME, 2, 3, order=order)
112 |
113 | def plotMonthlyStatsSleep(stats, columns=None):
114 | if not columns:
115 | columns = ['sleep_inefficiency', 'restless', 'sleep_hours',
116 | 'first_min_asleep']
117 | return _plotMonthlyStats(stats, columns, groupBy=True)
118 |
119 | def plotMonthlyStatsHb(stats):
120 | columns = ['count', 'mean', 'max', 'min', 'std']
121 | return _plotMonthlyStats(stats, columns, groupBy=False)
122 |
123 | def _plotMonthlyStats(stats, columns, groupBy=True):
124 | dataToPlot = stats.copy()
125 | # Group by month and rename date column
126 | if groupBy:
127 | dataToPlot = dataToPlot.groupby(stats['date'].dt.month).mean()
128 | dataToPlot = dataToPlot.reset_index().rename(columns={'date': 'month'})
129 |
130 | # change stats from columns to row attribute
131 | dataToPlot = pd.melt(dataToPlot, id_vars=['month'], value_vars=columns,
132 | var_name='stats', value_name='val')
133 | # Rename stats and weekdays
134 | dataToPlot['stats'].replace(NAMES, inplace=True)
135 | dataToPlot['month'].replace(months, inplace=True)
136 | order = [m for m in monthsOrder if m in dataToPlot['month'].unique()]
137 | # Plot
138 | g = sns.factorplot(data=dataToPlot, x="month", y="val", col="stats", order=order, kind="bar", sharey=False)
139 | g.set_xticklabels(rotation=45)
140 | g.set(xlabel='')
141 | return g
142 | #sns.plt.show()
143 |
144 | # def _plotMonthlyStats(stats, columns):
145 | # """
146 | # Plot aggregated (mean) stats by month
147 | # :param stats: data to plot
148 | # :param columns: columns from stats to plot
149 | # """
150 | # MEASURE_NAME = 'month'
151 | # months={1:'Jan', 2:'Feb', 3:'Mar', 4:'Apr', 5:'May', 6:'Jun', 7:'Jul', 8:'Aug',
152 | # 9:'Sep', 10:'Oct', 11:'Nov', 12:'Dec'}
153 | # order = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
154 | # stats[MEASURE_NAME] = stats[MEASURE_NAME].map(months)
155 | #
156 | # order = [m for m in order if m in stats[MEASURE_NAME].unique()]
157 | #
158 | # f, axes = getAxes(2,2)
159 | # for i, c in enumerate(columns):
160 | # if c in NAMES:
161 | # c = NAMES[c]
162 | # g = sns.barplot(x=MEASURE_NAME, y=c, data=stats, order=order, ax=axes[i])
163 | # g.set_xlabel('')
164 | # sns.plt.show()
165 |
166 | def _prepareYearAndMonthStats(stats, columns):
167 | # Group by month and change stats from columns to row attribute
168 | dataToPlot = stats.groupby(stats['date'].dt.to_period("M")).mean()
169 | dataToPlot = pd.melt(dataToPlot.reset_index(), id_vars=['date'], value_vars=columns,
170 | var_name='stats', value_name='val')
171 | # Rename stats
172 | dataToPlot['stats'].replace(NAMES, inplace=True)
173 | return dataToPlot
174 |
175 | def plotYearAndMonthStatsSleep(stats, columns=None):
176 | """
177 | Plot aggregated (mean) stats by year and month.
178 | :param stats: data to plot
179 | """
180 | if not columns:
181 | columns = ['sleep_efficiency', 'sleep_hours']
182 |
183 | dataToPlot = _prepareYearAndMonthStats(stats, columns)
184 | # Plot
185 | g = sns.factorplot(data=dataToPlot, x="date", y="val", row="stats", kind="point", sharey=False)
186 | g.set_xticklabels(rotation=45)
187 | for ax in g.axes.flat:
188 | ax.grid(b=True)
189 | return g
190 | #sns.plt.show()
191 |
192 | def _prepareDailyStats(stats, columns):
193 | dataToPlot = stats.rename(columns=NAMES)
194 |
195 | dates = pd.date_range(start=dataToPlot.date.iloc[0].date(), end=dataToPlot.date.iloc[-1].date())
196 | dataToPlot.set_index(['date'], inplace=True)
197 | dataToPlot = dataToPlot.reindex(dates)
198 | dataToPlot.reset_index(inplace=True)
199 | dataToPlot.rename(columns={'index': 'date'}, inplace=True)
200 | return dataToPlot
201 |
202 | def plotDailyStatsSleep(stats, columns=None):
203 | """
204 | Plot daily stats. Fill all data range, and put NaN for days without measures
205 | :param data: data to plot
206 | """
207 | MEASURE_NAME = 'date'
208 | if not columns:
209 | columns = ['sleep_inefficiency', 'sleep_hours']
210 | dataToPlot = _prepareDailyStats(stats, columns)
211 |
212 | f, axes = getAxes(2,1)
213 | xTicksDiv = min(10, len(dataToPlot))
214 | #xticks = [(x-pd.DateOffset(years=1, day=2)).date() for x in stats.date]
215 | xticks = [x.date() for x in dataToPlot.date]
216 | keptticks = xticks[::int(len(xticks)/xTicksDiv)]
217 | xticks = ['' for _ in xticks]
218 | xticks[::int(len(xticks)/xTicksDiv)] = keptticks
219 | for i, c in enumerate(columns):
220 | g =sns.pointplot(x=MEASURE_NAME, y=NAMES[c], data=dataToPlot, ax=axes[i])
221 | g.set_xticklabels([])
222 | g.set_xlabel('')
223 | g.set_xticklabels(xticks, rotation=45)
224 | sns.plt.show()
225 |
226 | def plotDailyStatsHb(data):
227 | ax = data.groupby(data[hbStats.NAME_DT_COL].dt.date).mean().plot()
228 | #data.groupby(data[hbStats.NAME_DT_COL].dt.date).mean().rolling(30).mean().plot(ax=ax)
229 | sns.plt.show()
230 |
231 | def plotYearMonthStatsHb(data):
232 | #pd.groupby(b,by=[b.index.month,b.index.year])
233 | data.groupby(pd.TimeGrouper(freq='M')).mean().plot()
234 | sns.plt.show()
235 |
236 | def plotSleepValueHeatmap(intradayStats, sleepValue=1):
237 | sns.set_context("poster")
238 | sns.set_style("darkgrid")
239 |
240 | xTicksDiv = 20
241 | #stepSize = int(len(xticks)/xTicksDiv)
242 | stepSize = 60
243 | xticks = [x for x in intradayStats.columns.values]
244 | keptticks = xticks[::stepSize]
245 | xticks = ['' for _ in xticks]
246 | xticks[::stepSize] = keptticks
247 | plt.figure(figsize=(16, 4.2))
248 | g = sns.heatmap(intradayStats.loc[sleepValue].reshape(1,-1))
249 | g.set_xticklabels(xticks, rotation=45)
250 | g.set_yticklabels([])
251 | g.set_ylabel(sleepStats.SLEEP_VALUES[sleepValue])
252 | plt.tight_layout()
253 | sns.plt.show()
254 |
255 | def plotCorrelation(stats):
256 | #columnsToDrop = ['sleep_interval_max_len', 'sleep_interval_min_len',
257 | # 'sleep_interval_avg_len', 'sleep_inefficiency',
258 | # 'sleep_hours', 'total_hours']
259 |
260 | #stats = stats.drop(columnsToDrop, axis=1)
261 |
262 | g = sns.PairGrid(stats)
263 | def corrfunc(x, y, **kws):
264 | r, p = scipystats.pearsonr(x, y)
265 | ax = plt.gca()
266 | ax.annotate("r = {:.2f}".format(r),xy=(.1, .9), xycoords=ax.transAxes)
267 | ax.annotate("p = {:.2f}".format(p),xy=(.2, .8), xycoords=ax.transAxes)
268 | if p>0.04:
269 | ax.patch.set_alpha(0.1)
270 |
271 | g.map_upper(plt.scatter)
272 | g.map_diag(plt.hist)
273 | g.map_lower(sns.kdeplot, cmap="Blues_d")
274 | g.map_upper(corrfunc)
275 | sns.plt.show()
276 |
277 | def getAxes(nrows, ncols):
278 | f, axes = plt.subplots(nrows=nrows, ncols=ncols)
279 | axes = axes.reshape(-1)
280 | return f, axes
281 |
282 | def plot(data, columns, measureName, nrows, ncols, order=None):
283 | f, axes = plt.subplots(nrows=nrows, ncols=ncols)
284 | axes = axes.reshape(-1)
285 | for i, c in enumerate(columns):
286 | sns.barplot(x=measureName, y=c, data=data, order=order, ax=axes[i])
287 | sns.plt.show()
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/src/resources/intraday_sleep_sample.csv:
--------------------------------------------------------------------------------
1 | 2016-03-21 23:18:00,2
2 | 2016-03-21 23:19:00,2
3 | 2016-03-21 23:20:00,2
4 | 2016-03-21 23:21:00,2
5 | 2016-03-21 23:22:00,2
6 | 2016-03-21 23:23:00,3
7 | 2016-03-21 23:24:00,2
8 | 2016-03-21 23:25:00,1
9 | 2016-03-21 23:26:00,1
10 | 2016-03-21 23:27:00,1
11 | 2016-03-21 23:28:00,1
12 | 2016-03-21 23:29:00,1
13 | 2016-03-21 23:30:00,1
14 | 2016-03-21 23:31:00,1
15 | 2016-03-21 23:32:00,1
16 | 2016-03-21 23:33:00,1
17 | 2016-03-21 23:34:00,1
18 | 2016-03-21 23:35:00,2
19 | 2016-03-21 23:36:00,1
20 | 2016-03-21 23:37:00,1
21 | 2016-03-21 23:38:00,1
22 | 2016-03-21 23:39:00,1
23 | 2016-03-21 23:40:00,1
24 | 2016-03-21 23:41:00,1
25 | 2016-03-21 23:42:00,1
26 | 2016-03-21 23:43:00,1
27 | 2016-03-21 23:44:00,1
28 | 2016-03-21 23:45:00,1
29 | 2016-03-21 23:46:00,1
30 | 2016-03-21 23:47:00,1
31 | 2016-03-21 23:48:00,1
32 | 2016-03-21 23:49:00,1
33 | 2016-03-21 23:50:00,1
34 | 2016-03-21 23:51:00,1
35 | 2016-03-21 23:52:00,1
36 | 2016-03-21 23:53:00,1
37 | 2016-03-21 23:54:00,1
38 | 2016-03-21 23:55:00,1
39 | 2016-03-21 23:56:00,1
40 | 2016-03-21 23:57:00,1
41 | 2016-03-21 23:58:00,1
42 | 2016-03-21 23:59:00,1
43 | 2016-03-22 00:00:00,1
44 | 2016-03-22 00:01:00,1
45 | 2016-03-22 00:02:00,1
46 | 2016-03-22 00:03:00,1
47 | 2016-03-22 00:04:00,1
48 | 2016-03-22 00:05:00,1
49 | 2016-03-22 00:06:00,1
50 | 2016-03-22 00:07:00,1
51 | 2016-03-22 00:08:00,1
52 | 2016-03-22 00:09:00,1
53 | 2016-03-22 00:10:00,1
54 | 2016-03-22 00:11:00,1
55 | 2016-03-22 00:12:00,1
56 | 2016-03-22 00:13:00,1
57 | 2016-03-22 00:14:00,1
58 | 2016-03-22 00:15:00,1
59 | 2016-03-22 00:16:00,1
60 | 2016-03-22 00:17:00,1
61 | 2016-03-22 00:18:00,2
62 | 2016-03-22 00:19:00,1
63 | 2016-03-22 00:20:00,1
64 | 2016-03-22 00:21:00,1
65 | 2016-03-22 00:22:00,1
66 | 2016-03-22 00:23:00,1
67 | 2016-03-22 00:24:00,1
68 | 2016-03-22 00:25:00,1
69 | 2016-03-22 00:26:00,1
70 | 2016-03-22 00:27:00,1
71 | 2016-03-22 00:28:00,1
72 | 2016-03-22 00:29:00,1
73 | 2016-03-22 00:30:00,1
74 | 2016-03-22 00:31:00,2
75 | 2016-03-22 00:32:00,1
76 | 2016-03-22 00:33:00,1
77 | 2016-03-22 00:34:00,1
78 | 2016-03-22 00:35:00,1
79 | 2016-03-22 00:36:00,1
80 | 2016-03-22 00:37:00,1
81 | 2016-03-22 00:38:00,1
82 | 2016-03-22 00:39:00,1
83 | 2016-03-22 00:40:00,1
84 | 2016-03-22 00:41:00,1
85 | 2016-03-22 00:42:00,1
86 | 2016-03-22 00:43:00,1
87 | 2016-03-22 00:44:00,1
88 | 2016-03-22 00:45:00,1
89 | 2016-03-22 00:46:00,1
90 | 2016-03-22 00:47:00,1
91 | 2016-03-22 00:48:00,1
92 | 2016-03-22 00:49:00,1
93 | 2016-03-22 00:50:00,1
94 | 2016-03-22 00:51:00,1
95 | 2016-03-22 00:52:00,1
96 | 2016-03-22 00:53:00,1
97 | 2016-03-22 00:54:00,1
98 | 2016-03-22 00:55:00,1
99 | 2016-03-22 00:56:00,1
100 | 2016-03-22 00:57:00,1
101 | 2016-03-22 00:58:00,1
102 | 2016-03-22 00:59:00,1
103 | 2016-03-22 01:00:00,1
104 | 2016-03-22 01:01:00,1
105 | 2016-03-22 01:02:00,1
106 | 2016-03-22 01:03:00,1
107 | 2016-03-22 01:04:00,1
108 | 2016-03-22 01:05:00,1
109 | 2016-03-22 01:06:00,1
110 | 2016-03-22 01:07:00,1
111 | 2016-03-22 01:08:00,1
112 | 2016-03-22 01:09:00,1
113 | 2016-03-22 01:10:00,1
114 | 2016-03-22 01:11:00,1
115 | 2016-03-22 01:12:00,2
116 | 2016-03-22 01:13:00,2
117 | 2016-03-22 01:14:00,2
118 | 2016-03-22 01:15:00,1
119 | 2016-03-22 01:16:00,1
120 | 2016-03-22 01:17:00,1
121 | 2016-03-22 01:18:00,1
122 | 2016-03-22 01:19:00,1
123 | 2016-03-22 01:20:00,1
124 | 2016-03-22 01:21:00,1
125 | 2016-03-22 01:22:00,1
126 | 2016-03-22 01:23:00,1
127 | 2016-03-22 01:24:00,1
128 | 2016-03-22 01:25:00,1
129 | 2016-03-22 01:26:00,1
130 | 2016-03-22 01:27:00,1
131 | 2016-03-22 01:28:00,2
132 | 2016-03-22 01:29:00,2
133 | 2016-03-22 01:30:00,1
134 | 2016-03-22 01:31:00,2
135 | 2016-03-22 01:32:00,2
136 | 2016-03-22 01:33:00,1
137 | 2016-03-22 01:34:00,1
138 | 2016-03-22 01:35:00,1
139 | 2016-03-22 01:36:00,1
140 | 2016-03-22 01:37:00,1
141 | 2016-03-22 01:38:00,1
142 | 2016-03-22 01:39:00,1
143 | 2016-03-22 01:40:00,1
144 | 2016-03-22 01:41:00,1
145 | 2016-03-22 01:42:00,1
146 | 2016-03-22 01:43:00,1
147 | 2016-03-22 01:44:00,1
148 | 2016-03-22 01:45:00,1
149 | 2016-03-22 01:46:00,1
150 | 2016-03-22 01:47:00,1
151 | 2016-03-22 01:48:00,1
152 | 2016-03-22 01:49:00,2
153 | 2016-03-22 01:50:00,2
154 | 2016-03-22 01:51:00,1
155 | 2016-03-22 01:52:00,1
156 | 2016-03-22 01:53:00,1
157 | 2016-03-22 01:54:00,1
158 | 2016-03-22 01:55:00,1
159 | 2016-03-22 01:56:00,1
160 | 2016-03-22 01:57:00,1
161 | 2016-03-22 01:58:00,1
162 | 2016-03-22 01:59:00,1
163 | 2016-03-22 02:00:00,1
164 | 2016-03-22 02:01:00,1
165 | 2016-03-22 02:02:00,2
166 | 2016-03-22 02:03:00,1
167 | 2016-03-22 02:04:00,1
168 | 2016-03-22 02:05:00,1
169 | 2016-03-22 02:06:00,1
170 | 2016-03-22 02:07:00,1
171 | 2016-03-22 02:08:00,1
172 | 2016-03-22 02:09:00,1
173 | 2016-03-22 02:10:00,1
174 | 2016-03-22 02:11:00,1
175 | 2016-03-22 02:12:00,1
176 | 2016-03-22 02:13:00,1
177 | 2016-03-22 02:14:00,1
178 | 2016-03-22 02:15:00,1
179 | 2016-03-22 02:16:00,1
180 | 2016-03-22 02:17:00,1
181 | 2016-03-22 02:18:00,1
182 | 2016-03-22 02:19:00,1
183 | 2016-03-22 02:20:00,1
184 | 2016-03-22 02:21:00,1
185 | 2016-03-22 02:22:00,1
186 | 2016-03-22 02:23:00,1
187 | 2016-03-22 02:24:00,1
188 | 2016-03-22 02:25:00,1
189 | 2016-03-22 02:26:00,1
190 | 2016-03-22 02:27:00,1
191 | 2016-03-22 02:28:00,1
192 | 2016-03-22 02:29:00,1
193 | 2016-03-22 02:30:00,1
194 | 2016-03-22 02:31:00,1
195 | 2016-03-22 02:32:00,1
196 | 2016-03-22 02:33:00,1
197 | 2016-03-22 02:34:00,1
198 | 2016-03-22 02:35:00,2
199 | 2016-03-22 02:36:00,2
200 | 2016-03-22 02:37:00,2
201 | 2016-03-22 02:38:00,1
202 | 2016-03-22 02:39:00,1
203 | 2016-03-22 02:40:00,1
204 | 2016-03-22 02:41:00,1
205 | 2016-03-22 02:42:00,1
206 | 2016-03-22 02:43:00,1
207 | 2016-03-22 02:44:00,1
208 | 2016-03-22 02:45:00,1
209 | 2016-03-22 02:46:00,1
210 | 2016-03-22 02:47:00,1
211 | 2016-03-22 02:48:00,1
212 | 2016-03-22 02:49:00,1
213 | 2016-03-22 02:50:00,1
214 | 2016-03-22 02:51:00,1
215 | 2016-03-22 02:52:00,1
216 | 2016-03-22 02:53:00,1
217 | 2016-03-22 02:54:00,1
218 | 2016-03-22 02:55:00,1
219 | 2016-03-22 02:56:00,1
220 | 2016-03-22 02:57:00,1
221 | 2016-03-22 02:58:00,1
222 | 2016-03-22 02:59:00,1
223 | 2016-03-22 03:00:00,1
224 | 2016-03-22 03:01:00,1
225 | 2016-03-22 03:02:00,1
226 | 2016-03-22 03:03:00,1
227 | 2016-03-22 03:04:00,1
228 | 2016-03-22 03:05:00,2
229 | 2016-03-22 03:06:00,1
230 | 2016-03-22 03:07:00,1
231 | 2016-03-22 03:08:00,1
232 | 2016-03-22 03:09:00,1
233 | 2016-03-22 03:10:00,1
234 | 2016-03-22 03:11:00,1
235 | 2016-03-22 03:12:00,1
236 | 2016-03-22 03:13:00,1
237 | 2016-03-22 03:14:00,1
238 | 2016-03-22 03:15:00,1
239 | 2016-03-22 03:16:00,1
240 | 2016-03-22 03:17:00,1
241 | 2016-03-22 03:18:00,1
242 | 2016-03-22 03:19:00,1
243 | 2016-03-22 03:20:00,1
244 | 2016-03-22 03:21:00,1
245 | 2016-03-22 03:22:00,1
246 | 2016-03-22 03:23:00,1
247 | 2016-03-22 03:24:00,1
248 | 2016-03-22 03:25:00,1
249 | 2016-03-22 03:26:00,1
250 | 2016-03-22 03:27:00,1
251 | 2016-03-22 03:28:00,1
252 | 2016-03-22 03:29:00,1
253 | 2016-03-22 03:30:00,1
254 | 2016-03-22 03:31:00,1
255 | 2016-03-22 03:32:00,1
256 | 2016-03-22 03:33:00,1
257 | 2016-03-22 03:34:00,1
258 | 2016-03-22 03:35:00,1
259 | 2016-03-22 03:36:00,2
260 | 2016-03-22 03:37:00,1
261 | 2016-03-22 03:38:00,1
262 | 2016-03-22 03:39:00,1
263 | 2016-03-22 03:40:00,1
264 | 2016-03-22 03:41:00,1
265 | 2016-03-22 03:42:00,1
266 | 2016-03-22 03:43:00,1
267 | 2016-03-22 03:44:00,1
268 | 2016-03-22 03:45:00,1
269 | 2016-03-22 03:46:00,2
270 | 2016-03-22 03:47:00,1
271 | 2016-03-22 03:48:00,1
272 | 2016-03-22 03:49:00,1
273 | 2016-03-22 03:50:00,1
274 | 2016-03-22 03:51:00,1
275 | 2016-03-22 03:52:00,1
276 | 2016-03-22 03:53:00,1
277 | 2016-03-22 03:54:00,1
278 | 2016-03-22 03:55:00,1
279 | 2016-03-22 03:56:00,1
280 | 2016-03-22 03:57:00,1
281 | 2016-03-22 03:58:00,1
282 | 2016-03-22 03:59:00,1
283 | 2016-03-22 04:00:00,1
284 | 2016-03-22 04:01:00,1
285 | 2016-03-22 04:02:00,1
286 | 2016-03-22 04:03:00,1
287 | 2016-03-22 04:04:00,1
288 | 2016-03-22 04:05:00,1
289 | 2016-03-22 04:06:00,1
290 | 2016-03-22 04:07:00,1
291 | 2016-03-22 04:08:00,1
292 | 2016-03-22 04:09:00,1
293 | 2016-03-22 04:10:00,1
294 | 2016-03-22 04:11:00,1
295 | 2016-03-22 04:12:00,1
296 | 2016-03-22 04:13:00,1
297 | 2016-03-22 04:14:00,1
298 | 2016-03-22 04:15:00,1
299 | 2016-03-22 04:16:00,1
300 | 2016-03-22 04:17:00,1
301 | 2016-03-22 04:18:00,1
302 | 2016-03-22 04:19:00,1
303 | 2016-03-22 04:20:00,1
304 | 2016-03-22 04:21:00,1
305 | 2016-03-22 04:22:00,1
306 | 2016-03-22 04:23:00,1
307 | 2016-03-22 04:24:00,1
308 | 2016-03-22 04:25:00,1
309 | 2016-03-22 04:26:00,1
310 | 2016-03-22 04:27:00,2
311 | 2016-03-22 04:28:00,1
312 | 2016-03-22 04:29:00,1
313 | 2016-03-22 04:30:00,1
314 | 2016-03-22 04:31:00,1
315 | 2016-03-22 04:32:00,1
316 | 2016-03-22 04:33:00,1
317 | 2016-03-22 04:34:00,1
318 | 2016-03-22 04:35:00,1
319 | 2016-03-22 04:36:00,1
320 | 2016-03-22 04:37:00,1
321 | 2016-03-22 04:38:00,1
322 | 2016-03-22 04:39:00,2
323 | 2016-03-22 04:40:00,1
324 | 2016-03-22 04:41:00,1
325 | 2016-03-22 04:42:00,1
326 | 2016-03-22 04:43:00,1
327 | 2016-03-22 04:44:00,1
328 | 2016-03-22 04:45:00,1
329 | 2016-03-22 04:46:00,1
330 | 2016-03-22 04:47:00,1
331 | 2016-03-22 04:48:00,1
332 | 2016-03-22 04:49:00,1
333 | 2016-03-22 04:50:00,1
334 | 2016-03-22 04:51:00,1
335 | 2016-03-22 04:52:00,1
336 | 2016-03-22 04:53:00,1
337 | 2016-03-22 04:54:00,1
338 | 2016-03-22 04:55:00,1
339 | 2016-03-22 04:56:00,1
340 | 2016-03-22 04:57:00,1
341 | 2016-03-22 04:58:00,1
342 | 2016-03-22 04:59:00,1
343 | 2016-03-22 05:00:00,1
344 | 2016-03-22 05:01:00,1
345 | 2016-03-22 05:02:00,1
346 | 2016-03-22 05:03:00,1
347 | 2016-03-22 05:04:00,1
348 | 2016-03-22 05:05:00,1
349 | 2016-03-22 05:06:00,1
350 | 2016-03-22 05:07:00,1
351 | 2016-03-22 05:08:00,1
352 | 2016-03-22 05:09:00,1
353 | 2016-03-22 05:10:00,1
354 | 2016-03-22 05:11:00,2
355 | 2016-03-22 05:12:00,1
356 | 2016-03-22 05:13:00,1
357 | 2016-03-22 05:14:00,1
358 | 2016-03-22 05:15:00,2
359 | 2016-03-22 05:16:00,1
360 | 2016-03-22 05:17:00,1
361 | 2016-03-22 05:18:00,1
362 | 2016-03-22 05:19:00,1
363 | 2016-03-22 05:20:00,1
364 | 2016-03-22 05:21:00,1
365 | 2016-03-22 05:22:00,1
366 | 2016-03-22 05:23:00,1
367 | 2016-03-22 05:24:00,1
368 | 2016-03-22 05:25:00,1
369 | 2016-03-22 05:26:00,1
370 | 2016-03-22 05:27:00,1
371 | 2016-03-22 05:28:00,1
372 | 2016-03-22 05:29:00,1
373 | 2016-03-22 05:30:00,1
374 | 2016-03-22 05:31:00,1
375 | 2016-03-22 05:32:00,1
376 | 2016-03-22 05:33:00,1
377 | 2016-03-22 05:34:00,1
378 | 2016-03-22 05:35:00,1
379 | 2016-03-22 05:36:00,1
380 | 2016-03-22 05:37:00,1
381 | 2016-03-22 05:38:00,1
382 | 2016-03-22 05:39:00,1
383 | 2016-03-22 05:40:00,1
384 | 2016-03-22 05:41:00,1
385 | 2016-03-22 05:42:00,1
386 | 2016-03-22 05:43:00,1
387 | 2016-03-22 05:44:00,2
388 | 2016-03-22 05:45:00,1
389 | 2016-03-22 05:46:00,1
390 | 2016-03-22 05:47:00,1
391 | 2016-03-22 05:48:00,1
392 | 2016-03-22 05:49:00,1
393 | 2016-03-22 05:50:00,1
394 | 2016-03-22 05:51:00,1
395 | 2016-03-22 05:52:00,1
396 | 2016-03-22 05:53:00,1
397 | 2016-03-22 05:54:00,1
398 | 2016-03-22 05:55:00,1
399 | 2016-03-22 05:56:00,1
400 | 2016-03-22 05:57:00,1
401 | 2016-03-22 05:58:00,1
402 | 2016-03-22 05:59:00,1
403 | 2016-03-22 06:00:00,1
404 | 2016-03-22 06:01:00,1
405 | 2016-03-22 06:02:00,1
406 | 2016-03-22 06:03:00,1
407 | 2016-03-22 06:04:00,1
408 | 2016-03-22 06:05:00,1
409 | 2016-03-22 06:06:00,1
410 | 2016-03-22 06:07:00,1
411 | 2016-03-22 06:08:00,1
412 | 2016-03-22 06:09:00,1
413 | 2016-03-22 06:10:00,1
414 | 2016-03-22 06:11:00,1
415 | 2016-03-22 06:12:00,1
416 | 2016-03-22 06:13:00,1
417 | 2016-03-22 06:14:00,1
418 | 2016-03-22 06:15:00,1
419 | 2016-03-22 06:16:00,1
420 | 2016-03-22 06:17:00,1
421 | 2016-03-22 06:18:00,1
422 | 2016-03-22 06:19:00,2
423 | 2016-03-22 06:20:00,1
424 | 2016-03-22 06:21:00,1
425 | 2016-03-22 06:22:00,1
426 | 2016-03-22 06:23:00,1
427 | 2016-03-22 06:24:00,1
428 | 2016-03-22 06:25:00,1
429 | 2016-03-22 06:26:00,1
430 | 2016-03-22 06:27:00,1
431 | 2016-03-22 06:28:00,1
432 | 2016-03-22 06:29:00,1
433 | 2016-03-22 06:30:00,1
434 | 2016-03-22 06:31:00,1
435 | 2016-03-22 06:32:00,1
436 | 2016-03-22 06:33:00,1
437 | 2016-03-22 06:34:00,1
438 | 2016-03-22 06:35:00,1
439 | 2016-03-22 06:36:00,1
440 | 2016-03-22 06:37:00,1
441 | 2016-03-22 06:38:00,1
442 | 2016-03-22 06:39:00,1
443 | 2016-03-22 06:40:00,1
444 | 2016-03-22 06:41:00,1
445 | 2016-03-22 06:42:00,1
446 | 2016-03-22 06:43:00,1
447 | 2016-03-22 06:44:00,1
448 | 2016-03-22 06:45:00,1
449 | 2016-03-22 06:46:00,1
450 | 2016-03-22 06:47:00,1
451 | 2016-03-22 06:48:00,1
452 | 2016-03-22 06:49:00,1
453 | 2016-03-22 06:50:00,1
454 | 2016-03-22 06:51:00,1
455 | 2016-03-22 06:52:00,2
456 | 2016-03-22 06:53:00,2
457 | 2016-03-22 06:54:00,1
458 | 2016-03-22 06:55:00,1
459 | 2016-03-22 06:56:00,2
460 | 2016-03-22 06:57:00,1
--------------------------------------------------------------------------------
/Sleep Dashboard.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Table of Contents\n",
8 | "* [Intro](#Intro)\n",
9 | "* [Load Data](#Load-Data)\n",
10 | "* [Generate and Save Stats](#Generate-and-Save-Stats)\n",
11 | "* [Basic Stats](#Basic-Stats)\n",
12 | "\t* [Preliminary Stats](#Preliminary-Stats)\n",
13 | "\t* [Weekday Stats](#Weekday-Stats)\n",
14 | "\t* [Monthly Stats](#Monthly-Stats)\n",
15 | "\t* [Year-Month Stats](#Year-Month-Stats)\n",
16 | "\t* [Weekday Stats By Month](#Weekday-Stats-By-Month)\n",
17 | "\t* [Daily Stats](#Daily-Stats)\n",
18 | "* [Intraday Stats](#Intraday-Stats)\n",
19 | "\t* [Daily Stats](#Daily-Stats)\n"
20 | ]
21 | },
22 | {
23 | "cell_type": "markdown",
24 | "metadata": {},
25 | "source": [
26 | "# Intro"
27 | ]
28 | },
29 | {
30 | "cell_type": "markdown",
31 | "metadata": {},
32 | "source": [
33 | "This notebook explores analysis and visualization of Fitbit sleep data."
34 | ]
35 | },
36 | {
37 | "cell_type": "code",
38 | "execution_count": null,
39 | "metadata": {
40 | "ExecuteTime": {
41 | "end_time": "2017-10-05T09:45:06.073764",
42 | "start_time": "2017-10-05T09:45:04.280662"
43 | },
44 | "collapsed": false
45 | },
46 | "outputs": [],
47 | "source": [
48 | "import os\n",
49 | "import sys\n",
50 | "from os.path import join\n",
51 | "\n",
52 | "import pandas as pd\n",
53 | "import numpy as np\n",
54 | "import seaborn as sns\n",
55 | "import time\n",
56 | "import datetime\n",
57 | "import matplotlib.pyplot as plt\n",
58 | "\n",
59 | "sys.path.append(os.path.join(os.getcwd(), \"src\"))\n",
60 | "from resources import RESOURCE_PATH\n",
61 | "from stats import sleepStats, combinedStats\n",
62 | "from util import utils, plotting as mplot\n",
63 | "\n",
64 | "%load_ext autoreload\n",
65 | "%autoreload 2\n",
66 | "\n",
67 | "%matplotlib notebook\n",
68 | "sns.set_context(\"paper\")\n",
69 | "\n",
70 | "dataFolder = \"path_to_your_fitbit_JSON_export\"\n",
71 | "statsFolder = join(dataFolder, os.path.pardir, 'folder_name_for_generated_stats')"
72 | ]
73 | },
74 | {
75 | "cell_type": "code",
76 | "execution_count": null,
77 | "metadata": {
78 | "ExecuteTime": {
79 | "end_time": "2017-10-05T09:45:11.609081",
80 | "start_time": "2017-10-05T09:45:10.588023"
81 | },
82 | "collapsed": false
83 | },
84 | "outputs": [],
85 | "source": [
86 | "import plotly.plotly as py\n",
87 | "from plotly.offline import init_notebook_mode, enable_mpl_offline, iplot_mpl\n",
88 | "import cufflinks as cf\n",
89 | "init_notebook_mode(connected=True)\n",
90 | "cf.go_offline(connected=True)\n",
91 | "enable_mpl_offline()\n",
92 | "\n",
93 | "from ipywidgets import interact, widgets\n",
94 | "#from IPython.display import display, clear_output"
95 | ]
96 | },
97 | {
98 | "cell_type": "code",
99 | "execution_count": null,
100 | "metadata": {
101 | "ExecuteTime": {
102 | "end_time": "2017-09-04T10:14:26.728995",
103 | "start_time": "2017-09-04T10:14:26.329972"
104 | },
105 | "collapsed": false
106 | },
107 | "outputs": [],
108 | "source": [
109 | "# Enable logging from Fitbit Analyer code\n",
110 | "import logging\n",
111 | "logger = logging.getLogger()\n",
112 | "logger.setLevel(logging.ERROR)\n",
113 | "logger.handlers[0].stream = sys.stdout"
114 | ]
115 | },
116 | {
117 | "cell_type": "markdown",
118 | "metadata": {},
119 | "source": [
120 | "# Load Data"
121 | ]
122 | },
123 | {
124 | "cell_type": "code",
125 | "execution_count": null,
126 | "metadata": {
127 | "ExecuteTime": {
128 | "end_time": "2017-10-05T09:45:17.824436",
129 | "start_time": "2017-10-05T09:45:17.487417"
130 | },
131 | "collapsed": true
132 | },
133 | "outputs": [],
134 | "source": [
135 | "# helper method to load provided test data\n",
136 | "def load_test_sleep_data():\n",
137 | " filepath = RESOURCE_PATH + \"\\\\unittest\\\\test_sleep_basic01.csv\"\n",
138 | " data1 = utils.loadIntradayData(filepath)\n",
139 | " filepath = RESOURCE_PATH + \"\\\\unittest\\\\test_sleep_basic02.csv\"\n",
140 | " data2 = utils.loadIntradayData(filepath)\n",
141 | " return [data1, data2]"
142 | ]
143 | },
144 | {
145 | "cell_type": "markdown",
146 | "metadata": {},
147 | "source": [
148 | "Load sleep data from raw JSON export of Fitbit records."
149 | ]
150 | },
151 | {
152 | "cell_type": "code",
153 | "execution_count": null,
154 | "metadata": {
155 | "ExecuteTime": {
156 | "end_time": "2017-10-05T09:45:19.714545",
157 | "start_time": "2017-10-05T09:45:19.379525"
158 | },
159 | "collapsed": false,
160 | "scrolled": true
161 | },
162 | "outputs": [],
163 | "source": [
164 | "start = time.time()\n",
165 | "#sleepData = utils.loadSleepData(dataFolder) # use for loading your own data\n",
166 | "sleepData = load_test_sleep_data() #use for testing\n",
167 | "end = time.time()\n",
168 | "print(\"Data loaded in {:.2f}s\".format(end - start))\n",
169 | "print(\"Loaded {} dataframes\".format(len(sleepData)))\n",
170 | "print(\"{} total entries\".format(np.sum([df.size for df in sleepData])))\n",
171 | "print(\"Sample from first dataframe:\")\n",
172 | "print(sleepData[0].head())"
173 | ]
174 | },
175 | {
176 | "cell_type": "markdown",
177 | "metadata": {},
178 | "source": [
179 | "# Generate and Save Stats"
180 | ]
181 | },
182 | {
183 | "cell_type": "markdown",
184 | "metadata": {},
185 | "source": [
186 | "For the loaded sleep data generate all currently avaiable stats:\n",
187 | "* **Basic Stats** (sleep values count, sleep efficiency, hours of sleep, total minutes in bed, N max-values for each stat)\n",
188 | "* **Timing Stats** (first minute asleep, to bed time, wake up time, sleep interval min/max length)\n",
189 | "* **Intervals Stats** for each day all the sleep intervals lengths\n",
190 | "* **Intraday Stats** minute to minute report for each day, for the specified sleep values. Total value count, with normalization and centering on specific time."
191 | ]
192 | },
193 | {
194 | "cell_type": "code",
195 | "execution_count": null,
196 | "metadata": {
197 | "ExecuteTime": {
198 | "end_time": "2017-10-05T09:55:04.813010",
199 | "start_time": "2017-10-05T09:55:04.373985"
200 | },
201 | "collapsed": false
202 | },
203 | "outputs": [],
204 | "source": [
205 | "start = time.time()\n",
206 | "basicAndTimingStats = sleepStats.generateStatsFrom(sleepData, sleepStats.STATS_NAME_BASIC_AND_TIMING)\n",
207 | "end = time.time()\n",
208 | "print(\"Computed basicAndTimingStats in {:.2f}s\".format(end - start))\n",
209 | "start = time.time()\n",
210 | "intervalsStats = sleepStats.generateStatsFrom(sleepData, sleepStats.STATS_NAME_INTERVALS)\n",
211 | "end = time.time()\n",
212 | "print(\"Computed intervalsStats in {:.2f}s\".format(end - start))\n",
213 | "start = time.time()\n",
214 | "intradayStats = sleepStats.generateStatsFrom(sleepData, sleepStats.STATS_NAME_INTRADAY)\n",
215 | "end = time.time()\n",
216 | "print(\"Computed intradayStats in {:.2f}s\".format(end - start))"
217 | ]
218 | },
219 | {
220 | "cell_type": "code",
221 | "execution_count": null,
222 | "metadata": {
223 | "collapsed": false
224 | },
225 | "outputs": [],
226 | "source": [
227 | "#print(basicAndTimingStats.head())\n",
228 | "#print(intervalsStats.head())\n",
229 | "#print(intradayStats.head())"
230 | ]
231 | },
232 | {
233 | "cell_type": "markdown",
234 | "metadata": {},
235 | "source": [
236 | "Save generated stats to file"
237 | ]
238 | },
239 | {
240 | "cell_type": "code",
241 | "execution_count": null,
242 | "metadata": {
243 | "ExecuteTime": {
244 | "end_time": "2017-10-05T09:55:16.364671",
245 | "start_time": "2017-10-05T09:55:16.006650"
246 | },
247 | "collapsed": false
248 | },
249 | "outputs": [],
250 | "source": [
251 | "today = datetime.date.today().strftime(\"%Y_%m_%d\")\n",
252 | "basicAndTimingStatsFilepath = os.path.join(statsFolder, \"basicAndTimingStats_{}.csv\".format(today))\n",
253 | "intervalsStatsFilepath = os.path.join(statsFolder, \"intervalStats_{}.csv\".format(today))\n",
254 | "intradayStatsFilepath = os.path.join(statsFolder, \"intradayStats_{}.csv\".format(today))\n",
255 | "\n",
256 | "basicAndTimingStats.to_csv(basicAndTimingStatsFilepath, index=False)\n",
257 | "intervalsStats.to_csv(intervalsStatsFilepath, index=False)\n",
258 | "intradayStats.to_csv(intradayStatsFilepath, index=False)"
259 | ]
260 | },
261 | {
262 | "cell_type": "markdown",
263 | "metadata": {},
264 | "source": [
265 | "# Basic Stats"
266 | ]
267 | },
268 | {
269 | "cell_type": "code",
270 | "execution_count": null,
271 | "metadata": {
272 | "ExecuteTime": {
273 | "end_time": "2017-09-05T16:55:58.997528",
274 | "start_time": "2017-09-05T16:55:58.725513"
275 | },
276 | "collapsed": false
277 | },
278 | "outputs": [],
279 | "source": [
280 | "# load basic and timing stats\n",
281 | "basicAndTimingStatsFilename = \"basicAndTimingStats_{}.csv\".format(today)\n",
282 | "basicAndTimingStats = pd.read_csv(os.path.join(statsFolder, basicAndTimingStatsFilename), \n",
283 | " parse_dates=['date', 'to_bed_time', 'wake_up_time'])\n",
284 | "basicAndTimingStats.head()"
285 | ]
286 | },
287 | {
288 | "cell_type": "code",
289 | "execution_count": null,
290 | "metadata": {
291 | "ExecuteTime": {
292 | "end_time": "2017-10-05T09:52:58.611792",
293 | "start_time": "2017-10-05T09:52:58.296774"
294 | },
295 | "collapsed": false
296 | },
297 | "outputs": [],
298 | "source": [
299 | "basicAndTimingStats.info()"
300 | ]
301 | },
302 | {
303 | "cell_type": "markdown",
304 | "metadata": {},
305 | "source": [
306 | "## Preliminary Stats"
307 | ]
308 | },
309 | {
310 | "cell_type": "code",
311 | "execution_count": null,
312 | "metadata": {
313 | "ExecuteTime": {
314 | "end_time": "2017-10-05T09:48:45.892337",
315 | "start_time": "2017-10-05T09:48:45.315304"
316 | },
317 | "collapsed": false,
318 | "scrolled": false
319 | },
320 | "outputs": [],
321 | "source": [
322 | "# plot preliminary stats (static plot) \n",
323 | "plot = mplot.plotPreliminaryStats(basicAndTimingStats)"
324 | ]
325 | },
326 | {
327 | "cell_type": "markdown",
328 | "metadata": {},
329 | "source": [
330 | "## Weekday Stats"
331 | ]
332 | },
333 | {
334 | "cell_type": "code",
335 | "execution_count": null,
336 | "metadata": {
337 | "ExecuteTime": {
338 | "end_time": "2017-10-05T09:48:52.637723",
339 | "start_time": "2017-10-05T09:48:52.285703"
340 | },
341 | "collapsed": false
342 | },
343 | "outputs": [],
344 | "source": [
345 | "# plot preliminary stats (static plot) \n",
346 | "plot = mplot.plotWeekdayStatsSleep(basicAndTimingStats)\n",
347 | "plot"
348 | ]
349 | },
350 | {
351 | "cell_type": "code",
352 | "execution_count": null,
353 | "metadata": {
354 | "ExecuteTime": {
355 | "end_time": "2017-09-05T16:56:42.641025",
356 | "start_time": "2017-09-05T16:56:41.759974"
357 | },
358 | "collapsed": false
359 | },
360 | "outputs": [],
361 | "source": [
362 | "# transform static plot using Plotly\n",
363 | "iplot_mpl(plot.fig)"
364 | ]
365 | },
366 | {
367 | "cell_type": "markdown",
368 | "metadata": {},
369 | "source": [
370 | "## Monthly Stats"
371 | ]
372 | },
373 | {
374 | "cell_type": "code",
375 | "execution_count": null,
376 | "metadata": {
377 | "ExecuteTime": {
378 | "end_time": "2017-10-05T09:46:55.711035",
379 | "start_time": "2017-10-05T09:46:55.341014"
380 | },
381 | "collapsed": false
382 | },
383 | "outputs": [],
384 | "source": [
385 | "plot = mplot.plotMonthlyStatsSleep(basicAndTimingStats)\n",
386 | "plot"
387 | ]
388 | },
389 | {
390 | "cell_type": "markdown",
391 | "metadata": {},
392 | "source": [
393 | "## Year-Month Stats"
394 | ]
395 | },
396 | {
397 | "cell_type": "code",
398 | "execution_count": null,
399 | "metadata": {
400 | "ExecuteTime": {
401 | "end_time": "2017-10-05T09:47:27.207837",
402 | "start_time": "2017-10-05T09:47:26.808814"
403 | },
404 | "collapsed": false
405 | },
406 | "outputs": [],
407 | "source": [
408 | "# plot Year-Month stats (static plot)\n",
409 | "plot = mplot.plotYearAndMonthStatsSleep(basicAndTimingStats)"
410 | ]
411 | },
412 | {
413 | "cell_type": "code",
414 | "execution_count": null,
415 | "metadata": {
416 | "ExecuteTime": {
417 | "end_time": "2017-09-05T17:42:43.098914",
418 | "start_time": "2017-09-05T17:42:41.924847"
419 | },
420 | "collapsed": false
421 | },
422 | "outputs": [],
423 | "source": [
424 | "# interactive single year-month stat\n",
425 | "@interact(stat_name=mplot.NAMES.keys())\n",
426 | "def iplot_yearAndMonthStats(stat_name):\n",
427 | " data = basicAndTimingStats.groupby(basicAndTimingStats['date'].dt.to_period(\"M\"))[[stat_name]].mean()\n",
428 | " data.iplot(title='Year-Month Average - {}'.format(mplot.NAMES[stat_name]))"
429 | ]
430 | },
431 | {
432 | "cell_type": "markdown",
433 | "metadata": {},
434 | "source": [
435 | "## Weekday Stats By Month"
436 | ]
437 | },
438 | {
439 | "cell_type": "code",
440 | "execution_count": null,
441 | "metadata": {
442 | "ExecuteTime": {
443 | "end_time": "2017-09-05T17:51:11.524994",
444 | "start_time": "2017-09-05T17:51:10.967962"
445 | },
446 | "collapsed": false
447 | },
448 | "outputs": [],
449 | "source": [
450 | "# plot weekday stats by month (static plot) \n",
451 | "plot = mplot.plotWeekdayStatsByMonthSleep(basicAndTimingStats)"
452 | ]
453 | },
454 | {
455 | "cell_type": "code",
456 | "execution_count": null,
457 | "metadata": {
458 | "ExecuteTime": {
459 | "end_time": "2017-09-05T17:51:22.286610",
460 | "start_time": "2017-09-05T17:51:21.927589"
461 | },
462 | "collapsed": true
463 | },
464 | "outputs": [],
465 | "source": [
466 | "weekdayByMonthStats = mplot._prepareWeekdayByMonthStats(basicAndTimingStats)"
467 | ]
468 | },
469 | {
470 | "cell_type": "code",
471 | "execution_count": null,
472 | "metadata": {
473 | "ExecuteTime": {
474 | "end_time": "2017-09-05T18:06:22.766114",
475 | "start_time": "2017-09-05T18:06:22.357091"
476 | },
477 | "collapsed": false
478 | },
479 | "outputs": [],
480 | "source": [
481 | "@interact(stat_name=mplot.NAMES.keys())\n",
482 | "def iplot_weekdayByMonthStats(stat_name):\n",
483 | " weekdayByMonthStats.pivot('day', 'month', values=stat_name).iplot(title='Weekday By Month - {}'.format(mplot.NAMES[stat_name]),\n",
484 | " xTitle='Weekday', yTitle=mplot.NAMES[stat_name])"
485 | ]
486 | },
487 | {
488 | "cell_type": "markdown",
489 | "metadata": {},
490 | "source": [
491 | "## Daily Stats"
492 | ]
493 | },
494 | {
495 | "cell_type": "code",
496 | "execution_count": null,
497 | "metadata": {
498 | "ExecuteTime": {
499 | "end_time": "2017-09-05T18:03:12.380225",
500 | "start_time": "2017-09-05T18:03:11.974201"
501 | },
502 | "collapsed": false
503 | },
504 | "outputs": [],
505 | "source": [
506 | "@interact(stat_name=mplot.NAMES.keys())\n",
507 | "def iplot_weekdayByMonthStats(stat_name):\n",
508 | " data = basicAndTimingStats[['date', stat_name]].set_index(['date'])\n",
509 | " data.iplot(title='Daily Stats - {}'.format(mplot.NAMES[stat_name]), yTitle=mplot.NAMES[stat_name])"
510 | ]
511 | },
512 | {
513 | "cell_type": "markdown",
514 | "metadata": {},
515 | "source": [
516 | "# Intraday Stats"
517 | ]
518 | },
519 | {
520 | "cell_type": "code",
521 | "execution_count": null,
522 | "metadata": {
523 | "collapsed": false
524 | },
525 | "outputs": [],
526 | "source": [
527 | "intradayStatsFilename = \"intradayStats_{}.csv\".format(today)\n",
528 | "intradayStats = pd.read_csv(os.path.join(statsFolder, intradayStats), \n",
529 | " parse_dates=['date', 'to_bed_time', 'wake_up_time'])\n",
530 | "intradayStats.drop(\"date\", axis=1, inplace=True)\n",
531 | "data = intradayStats.apply(pd.value_counts)\n",
532 | "#mplot.plotSleepValueHeatmap(data, sleepValue=1)"
533 | ]
534 | },
535 | {
536 | "cell_type": "code",
537 | "execution_count": null,
538 | "metadata": {
539 | "collapsed": false
540 | },
541 | "outputs": [],
542 | "source": [
543 | "normIntradayCountStats = sleepStats.normalizedIntradayCountStats(intradayStats)\n",
544 | "centeredIntradayCountStats = sleepStats.centerIntradayCountStats(normIntradayCountStats)\n",
545 | "#mplot.plotSleepValueHeatmap(centeredIntradayCountStats, sleepValue=1)"
546 | ]
547 | },
548 | {
549 | "cell_type": "markdown",
550 | "metadata": {},
551 | "source": [
552 | "## Daily Stats\n"
553 | ]
554 | },
555 | {
556 | "cell_type": "code",
557 | "execution_count": null,
558 | "metadata": {
559 | "collapsed": false
560 | },
561 | "outputs": [],
562 | "source": [
563 | "#stats.set_index('date', inplace=True)\n",
564 | "stats['sleep_efficiency_rol_mean'] = stats['sleep_efficiency'].rolling(center=False,window=20).mean()\n",
565 | "stats['sleep_efficiency'].plot()\n",
566 | "stats['sleep_efficiency_rol_mean'].plot()\n",
567 | "sns.plt.show()"
568 | ]
569 | },
570 | {
571 | "cell_type": "code",
572 | "execution_count": null,
573 | "metadata": {
574 | "collapsed": false
575 | },
576 | "outputs": [],
577 | "source": [
578 | "testData = stats['restless']\n",
579 | "testData.resample('20D').mean().plot()\n",
580 | "testData.plot()\n",
581 | "sns.plt.show()"
582 | ]
583 | }
584 | ],
585 | "metadata": {
586 | "anaconda-cloud": {},
587 | "kernelspec": {
588 | "display_name": "Python [conda env:fitbit-analyzer]",
589 | "language": "python",
590 | "name": "conda-env-fitbit-analyzer-py"
591 | },
592 | "language_info": {
593 | "codemirror_mode": {
594 | "name": "ipython",
595 | "version": 3
596 | },
597 | "file_extension": ".py",
598 | "mimetype": "text/x-python",
599 | "name": "python",
600 | "nbconvert_exporter": "python",
601 | "pygments_lexer": "ipython3",
602 | "version": "3.5.1"
603 | },
604 | "toc": {
605 | "colors": {
606 | "hover_highlight": "#DAA520",
607 | "running_highlight": "#FF0000",
608 | "selected_highlight": "#FFD700"
609 | },
610 | "moveMenuLeft": true,
611 | "nav_menu": {
612 | "height": "99px",
613 | "width": "252px"
614 | },
615 | "navigate_menu": true,
616 | "number_sections": true,
617 | "sideBar": true,
618 | "threshold": 4,
619 | "toc_cell": false,
620 | "toc_section_display": "block",
621 | "toc_window_display": true
622 | }
623 | },
624 | "nbformat": 4,
625 | "nbformat_minor": 0
626 | }
627 |
--------------------------------------------------------------------------------