Guide to Extracting Data w/ APIs from the Fitbit Charge 4§
Start coding now! Visit the Colab Notebook
A picture of the Fitbit Charge 4 that was used for this notebook
The Fitbit Charge 4 is a Sleep and Physical Activity tracker consisting of 15 advanced sensors and 16 MB of storage capacity. Read more about the Fitbit Charge 4 here. An updated version, the Fitbit Charge 5, is also available now.
This is a comprehensive, clear guide to extract data from the Fitbit Charge 4 using the Fitbit Web API. Links to external resources and official Fitbit documentation are provided sporadically throughout the guide for further reference.
If you want to know more about the Fitbit, see the README for a detailed analysis of performances, sensors, data privacy, and extraction pipelines. A list of the most important accessible data categories is provided below, For the full list, access the api data in section 3.
Category Name (API version) |
Parameter Name (subcategory) |
Frequency of Sampling |
|---|---|---|
sleep |
date |
during the night |
sleep |
duration |
during the night |
sleep |
efficiency |
during the night |
sleep |
end time |
during the night |
sleep |
sleep levels |
during the night |
steps |
date and time |
daily |
steps |
value (number of steps) |
daily |
minutesVeryActive |
date and time |
daily |
minutesVeryActive |
value |
daily |
minutesFairlyActive |
date and time |
daily |
minutesFairlyActive |
value |
daily |
minutesLightlyActive |
date and time |
daily |
minutesLightlyActive |
value |
daily |
distance moved |
date and time |
daily |
distance moved |
value |
daily |
minutesSedentary |
date and time |
daily |
minutesSedentary |
value |
daily |
heart rate |
resting heart rate |
daily (per minute) |
heart rate |
heart rate zones |
daily (per minute) |
heart rate |
heart rate variability |
during sleep (per minute) |
temperature |
skin temperature |
daily |
temperature |
core temperature |
daily |
Spo2 |
date and time |
during sleep |
Spo2 |
value |
during sleep |
In this guide, we sequentially cover the following five topics to extract data from the Fitbit API:
Set up
Authentication/Authorization
Data extraction
Data visualization
Data analysis
*Note: Full documentation of APIs by Fitbit can be found here.
1. Set up§
Relevant libraries are imported below.
[ ]:
import base64
import hashlib
import html
import json
import os
import re
import urllib.parse
import requests
import matplotlib.pyplot as plt
import pandas as pd
from sklearn.covariance import EllipticEnvelope
import seaborn as sns
from scipy import stats
2. Authentication/Authorization§
To obtain access to the data using the Web API, authentication and authorization is required. Fitbit supports the OAuth 2.0 protocol, with three different models (read more about it here).
Briefly, Fitbit offers three workflows for their Web APIs in order of lowest level of security to the highest: * Implicit Grant Flow * Authorization Code Grant Flow * Authorization Code Grant Flow with PKCE
We will discuss two authenication/authorization methods, namely the Implicit Grant Flow and the Authorization Code Grant Flow with PKCE.
The full documentation for all workflows are provided here.
2.1 Implict Grant Flow§
2.2 Authorization Code Grant Flow§
[ ]:
code_verifier = base64.urlsafe_b64encode(
os.urandom(43)
).decode("utf-8") if "code_verifier" not in locals() else code_verifier
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode("utf-8")).digest()
).decode("utf-8").replace('=', '')
2.2.1 Registering an application§
First, register an application on here while logged in. OAuth 2.0 Application Type should be set to Client or Personal and the Callback URL is the address through which you can receive your token (https://127.0.0.1/, also known as the localhost, is provided as an example, but any link accessible locally should suffice; 8080 is the port). Other sections can be filled without particular specifications (e.g. https://google.com for all website links). An image with the important sections highlight are provided below for clarity.
The client_id and client_secret can be accessed under Manage My Apps.
An image is provided below for clarity.
Afterwards, we initialize a dictionary to hold all variables relevant to authenticating, authorizing, and calling the API later.
[ ]:
variables = dict()
# user specified
variables["client_id"] = "238B5J"
variables["client_secret"] = "0c4059cc38121dfdaafb12c02fee2a9c"
variables["expires_in"] = "31536000" # expiry of token in seconds
# constants or one-time generated
variables["code_verifier"] = code_verifier
variables["code_challenge"] = code_challenge
variables["code_challenge_method"] = "S256"
variables["response_type"] = "token" # code
variables["scope"] = (
"weight%20location%20settings%20profile%20nutrition%20" +
"activity%20sleep%20heartrate%20social"
)
variables["prompt"] = "none"
variables["redirect_uri"] = "https%3A%2F%2F127.0.0.1%3A8080%2F"
variables["grant_type"] = "authorization_code"
variables["authorization"] = base64.urlsafe_b64encode(
bytes(variables["client_id"] + ":" + variables["client_secret"], "utf-8")
).decode("utf-8")
[ ]:
for i in variables.items():
print(i)
('client_id', '238B5J')
('client_secret', '0c4059cc38121dfdaafb12c02fee2a9c')
('expires_in', '31536000')
('code_verifier', 'zV1XXK_iHDBYo8ZBtDhzlsqZ7q_rZKW8tsTTpqzvpQvaKWdr_AboPjKwxw==')
('code_challenge', '8xM1zi7CR8u-Iz51MUg5D6gSyvkf3CR8GvsGSeyrOF4')
('code_challenge_method', 'S256')
('response_type', 'token')
('scope', 'weight%20location%20settings%20profile%20nutrition%20activity%20sleep%20heartrate%20social')
('prompt', 'none')
('redirect_uri', 'https%3A%2F%2F127.0.0.1%3A8080%2F')
('grant_type', 'authorization_code')
('authorization', 'MjM4QjVKOjBjNDA1OWNjMzgxMjFkZmRhYWZiMTJjMDJmZWUyYTlj')
2.2.2 Authorize the App§
Next, we display Fitbit’s authorization page by typing a specific URL on a web browser. A code challenge and code verifier is required to progress further. The concept is comprehensively outlined here.
The URL should consist of the following required parameters (split into “variable” and “non-variable” parameters).
Variable parameters§
Non-variable parameters§
scope: space-delimited list of data collections requested by the applicationresponse_type: code
The resulting URL is demonstrated below.
[ ]:
# combine all parameters into the url string
url = "https://www.fitbit.com/oauth2/authorize" # authorization endpoint
for key in ["client_id", "redirect_uri", "code_challenge", "code_challenge_method", "scope", "response_type", "expires_in"]:
if url == "https://www.fitbit.com/oauth2/authorize":
url += "?" + key + "=" + variables[key]
else:
url += "&" + key + "=" + variables[key]
print(url)
https://www.fitbit.com/oauth2/authorize?client_id=238B5J&redirect_uri=https%3A%2F%2F127.0.0.1%3A8080%2F&code_challenge=8xM1zi7CR8u-Iz51MUg5D6gSyvkf3CR8GvsGSeyrOF4&code_challenge_method=S256&scope=weight%20location%20settings%20profile%20nutrition%20activity%20sleep%20heartrate%20social&response_type=token&expires_in=31536000
Click the url above to access the Authorization page. Check Allow All and click the Allow button in red. An image is provided below for clarity.
2.2.3 Retrieving the Authorization Code§
If the response_type is token, an access_token is provided to you as part of the url.
In the example above, the access_token is eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIyMzhCNUoiLCJzdWIiOiI5RkcyNkwiLCJpc3MiOiJGaXRiaXQiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4iLCJzY29wZXMiOiJyc29jIHJzZXQgcmFjdCBybG9jIHJ3ZWkgcmhyIHJwcm8gcm51dCByc2xlIiwiZXhwIjoxNjgyNTIwNjI5LCJpYXQiOjE2NTMwNzE3MTZ9.JPCUw1hMstRMNHgdiHbBmmY-a7o_yX_m6Zx_KaY1J1c.
Store the access_code inside the variables dictionary and skip 2.2.4.
[ ]:
variables["access_token"] = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIyMzhCNUoiLCJzdWIiOiI5RkcyNkwiLCJpc3MiOiJGaXRiaXQiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4iLCJzY29wZXMiOiJyc29jIHJzZXQgcmFjdCBybG9jIHJ3ZWkgcmhyIHJwcm8gcm51dCByc2xlIiwiZXhwIjoxNjgyNTIwNjI5LCJpYXQiOjE2NTMwNzE3MTZ9.JPCUw1hMstRMNHgdiHbBmmY-a7o_yX_m6Zx_KaY1J1c"
If the response_type is code, an access_code is provided to you as part of the url.
https://127.0.0.1:8080/?code=e25839197271c1d6f7dab497ff43f0e0610d1970#=
In the example above, the access_code is e25839197271c1d6f7dab497ff43f0e0610d1970.
Store the access_code inside the variables dictionary and do not skip 2.2.4.
[ ]:
# variables["access_code"] = "9e12294aa2f8405dd70ef68e3f3408bfbdc19a57"
2.2.4 (OPTIONAL) Exchange the Authorization Code for an Access Token§
We exchange the authorization cdoe for access and refresh tokens. First, define a function that makes a post request on the API with the authorization code.
[ ]:
# # executes a POST request on the API to obtain access tokens
# def get_access_token(
# authorization: str,
# client_id: str,
# code: str,
# code_verifier: str,
# grant_type: str,
# redirect_uri: str,
# url: str = "https://api.fitbit.com/oauth2/token",
# call: str = "POST"
# ):
# headers = {
# "Authorization": "Basic " + authorization,
# "Content-Type": "application/x-www-form-urlencoded"
# }
# params = {
# "client_id": client_id,
# "code": code,
# "code_verifier": code_verifier,
# "grant_type": grant_type,
# "redirect_uri": redirect_uri
# }
# return requests.request(
# call, url=url, params=params, headers=headers).json()
[ ]:
# headers = {
# "Authorization": "Basic " + variables["authorization"],
# "Content-Type": "application%2Fx-www-form-urlencoded"
# }
# params = {
# "clientId": variables["client_id"],
# "code": variables["access_code"],
# "grant_type": variables["grant_type"],
# "redirect_uri": variables["redirect_uri"]
# }
# response = requests.request("POST", url="https://api.fitbit.com/oauth2/token", params=params, headers=headers).json()
# response
The required parameters are again split into “variable” and “non-variable” parameters. Variable parameters, however, are specified by previous steps.
Variable parameters§
client_id: Fitbit API application ID, already specified (under manage my apps specified in 2.1, link here)code: authorization codecode_verifier: code verifier value
Non-variable parameters§
grant_type: authorization code
[ ]:
# get_access_token(
# authorization=variables["authorization"],
# client_id=variables["client_id"],
# code=variables["code"],
# code_verifier=variables["code_verifier"],
# grant_type=variables["grant_type"],
# redirect_uri=variables["redirect_uri"]
# )
[ ]:
# variables["access_token"] = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIyMzhCNlMiLCJzdWIiOiI5RkcyNkwiLCJpc3MiOiJGaXRiaXQiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4iLCJzY29wZXMiOiJyc29jIHJzZXQgcmFjdCBybG9jIHJ3ZWkgcmhyIHJwcm8gcm51dCByc2xlIiwiZXhwIjoxNjUwOTExMTA2LCJpYXQiOjE2NTAzMTM1NTl9.vFS3mWguPD4_8FMSpClBP9UA212KfwD2eSurYmYGuDM"
2.2.5 Calling the API§
[ ]:
# executes a GET request on the API
def call_API(
access_token: str,
url: str,
call: str = "GET"
):
headers = {
"Authorization": "Bearer " + access_token
}
return requests.request(
call, url=url, headers=headers).json()
[ ]:
# calls user profile
call_API(
access_token=variables["access_token"],
url="https://api.fitbit.com/1/user/-/profile.json"
)#["user"].keys()
{'user': {'age': 65,
'ambassador': False,
'autoStrideEnabled': True,
'avatar': 'https://static0.fitbit.com/images/profile/defaultProfile_100.png',
'avatar150': 'https://static0.fitbit.com/images/profile/defaultProfile_150.png',
'avatar640': 'https://static0.fitbit.com/images/profile/defaultProfile_640.png',
'averageDailySteps': 0,
'challengesBeta': True,
'clockTimeDisplayFormat': '12hour',
'corporate': False,
'corporateAdmin': False,
'dateOfBirth': '1956-12-14',
'displayName': 'Peter N.',
'displayNameSetting': 'name',
'distanceUnit': 'en_US',
'encodedId': '9FG26L',
'features': {'exerciseGoal': True},
'firstName': 'Peter',
'foodsLocale': 'en_US',
'fullName': 'Peter Norvig',
'gender': 'MALE',
'glucoseUnit': 'en_US',
'height': 190.5,
'heightUnit': 'en_US',
'isBugReportEnabled': False,
'isChild': False,
'isCoach': False,
'languageLocale': 'en_US',
'lastName': 'Norvig',
'legalTermsAcceptRequired': True,
'locale': 'en_US',
'memberSince': '2021-05-25',
'mfaEnabled': False,
'offsetFromUTCMillis': 7200000,
'sdkDeveloper': False,
'sleepTracking': 'Normal',
'startDayOfWeek': 'SUNDAY',
'strideLengthRunning': 96.2,
'strideLengthRunningType': 'auto',
'strideLengthWalking': 79.10000000000001,
'strideLengthWalkingType': 'auto',
'swimUnit': 'en_US',
'temperatureUnit': 'en_US',
'timezone': 'Africa/Cairo',
'topBadges': [{'badgeGradientEndColor': 'A489E8',
'badgeGradientStartColor': '38216E',
'badgeType': 'DAILY_STEPS',
'category': 'Daily Steps',
'cheers': [],
'dateTime': '2022-04-23',
'description': '20,000 steps in a day',
'earnedMessage': 'Congrats on earning your first High Tops badge!',
'encodedId': '228TPP',
'image100px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/100px/badge_daily_steps20k.png',
'image125px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/125px/badge_daily_steps20k.png',
'image300px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/300px/badge_daily_steps20k.png',
'image50px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/badge_daily_steps20k.png',
'image75px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/75px/badge_daily_steps20k.png',
'marketingDescription': "You've walked 20,000 steps And earned the High Tops badge!",
'mobileDescription': "When it comes to steps, it looks like you're not playing around. This achievement was a slam dunk.",
'name': 'High Tops (20,000 steps in a day)',
'shareImage640px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/386px/shareLocalized/en_US/badge_daily_steps20k.png',
'shareText': 'I took 20,000 steps and earned the High Tops badge! #Fitbit',
'shortDescription': '20,000 steps',
'shortName': 'High Tops',
'timesAchieved': 1,
'value': 20000},
{'badgeGradientEndColor': 'FFDB01',
'badgeGradientStartColor': 'D99123',
'badgeType': 'LIFETIME_DISTANCE',
'category': 'Lifetime Distance',
'cheers': [],
'dateTime': '2022-04-21',
'description': '990 lifetime miles',
'earnedMessage': "Whoa! You've earned the New Zealand badge!",
'encodedId': '22B8MD',
'image100px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/100px/badge_lifetime_miles990.png',
'image125px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/125px/badge_lifetime_miles990.png',
'image300px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/300px/badge_lifetime_miles990.png',
'image50px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/badge_lifetime_miles990.png',
'image75px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/75px/badge_lifetime_miles990.png',
'marketingDescription': "By reaching 990 lifetime miles, you've earned the New Zealand badge!",
'mobileDescription': "You've walked the entire length of New Zealand.",
'name': 'New Zealand (990 lifetime miles)',
'shareImage640px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/386px/shareLocalized/en_US/badge_lifetime_miles990.png',
'shareText': 'I covered 990 miles with my #Fitbit and earned the New Zealand badge.',
'shortDescription': '990 miles',
'shortName': 'New Zealand',
'timesAchieved': 1,
'unit': 'MILES',
'value': 990},
{'badgeGradientEndColor': '38D7FF',
'badgeGradientStartColor': '2DB4D7',
'badgeType': 'DAILY_FLOORS',
'category': 'Daily Climb',
'cheers': [],
'dateTime': '2022-04-30',
'description': '125 floors in a day',
'earnedMessage': 'Congrats on earning your first Rollercoaster badge!',
'encodedId': '229844',
'image100px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/100px/badge_daily_floors125.png',
'image125px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/125px/badge_daily_floors125.png',
'image300px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/300px/badge_daily_floors125.png',
'image50px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/badge_daily_floors125.png',
'image75px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/75px/badge_daily_floors125.png',
'marketingDescription': "You've climbed 125 floors to earn the Rollercoaster badge!",
'mobileDescription': "That's hair-raising, jaw-dropping, mind-blowing floor count!",
'name': 'Rollercoaster (125 floors in a day)',
'shareImage640px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/386px/shareLocalized/en_US/badge_daily_floors125.png',
'shareText': 'I climbed 125 flights of stairs and earned the Rollercoaster badge! #Fitbit',
'shortDescription': '125 floors',
'shortName': 'Rollercoaster',
'timesAchieved': 2,
'value': 125},
{'badgeGradientEndColor': 'FFDB01',
'badgeGradientStartColor': 'D99123',
'badgeType': 'LIFETIME_FLOORS',
'category': 'Lifetime Climb',
'cheers': [],
'dateTime': '2022-04-30',
'description': '4,000 lifetime floors',
'earnedMessage': "Yipee! You've earned the 747 badge!",
'encodedId': '228TKR',
'image100px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/100px/badge_lifetime_floors4k.png',
'image125px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/125px/badge_lifetime_floors4k.png',
'image300px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/300px/badge_lifetime_floors4k.png',
'image50px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/badge_lifetime_floors4k.png',
'image75px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/75px/badge_lifetime_floors4k.png',
'marketingDescription': "By climbing 4000 lifetime floors, you've earned the 747 badge!",
'mobileDescription': 'Your lifetime badges are really taking flight, because you just jetsetted your way to another badge!',
'name': '747 (4,000 lifetime floors)',
'shareImage640px': 'https://www.gstatic.com/fitbit/badge/images/badges_new/386px/shareLocalized/en_US/badge_lifetime_floors4k.png',
'shareText': 'I climbed 4,000 floors with my #Fitbit and earned the 747 badge.',
'shortDescription': '4,000 floors',
'shortName': '747',
'timesAchieved': 1,
'value': 4000}],
'waterUnit': 'en_US',
'waterUnitName': 'fl oz',
'weight': 92.9,
'weightUnit': 'en_US'}}
2.2.6 Refresh Tokens§
Tokens have a specified TTL (time to live) determined earlier by the expires parameter. Once that time is over, a new token must be issued.
3. Data extraction§
Now, we can extract data by calling the API using the call_API() function. A full list of data types and endpoints are available here.
In brief, the categories are: * Activity * Activity Intraday Time Series * Activity Time Series * Body and Weight * Body and Weight Time Series * Devices * Food and Water * Food and Water Time Series * Friends * Heart Rate Intraday Time Series * Heart Rate Time Series * Sleep * Subscriptions * User
Authorization is a post only endpoint as demonstrated in previous sections (used to obtain access token). * POST-ONLY: Auth
[ ]:
#@title Set up the start and end dates (YYYY-MM-DD)
start_date = "2022-05-15" #@param {type:"string"}
end_date = "2022-05-25" #@param {type:"string"}
# store arguments for some of the categories
categories = {
"sleep": {
"url": "https://api.fitbit.com/1.2/user/-/sleep/date/" + start_date + "/" + end_date + ".json"},
"steps": {
"url": "https://api.fitbit.com/1/user/-/activities/steps/date/" + start_date + "/" + end_date + ".json"},
"minutesVeryActive": {
"url": "https://api.fitbit.com/1/user/-/activities/minutesVeryActive/date/" + start_date + "/" + end_date + ".json"},
"minutesFairlyActive": {
"url": "https://api.fitbit.com/1/user/-/activities/minutesFairlyActive/date/" + start_date + "/" + end_date + ".json"},
"minutesLightlyActive": {
"url": "https://api.fitbit.com/1/user/-/activities/minutesLightlyActive/date/" + start_date + "/" + end_date + ".json"},
"distance": {
"url": "https://api.fitbit.com/1/user/-/activities/distance/date/" + start_date + "/" + end_date + ".json"},
"minutesSedentary": {
"url": "https://api.fitbit.com/1/user/-/activities/minutesSedentary /date/" + start_date + "/" + end_date + ".json"},
}
# initialize empty dictionary to aggregate values
api_data = dict()
# loop api calls for all categories
for category, values in categories.items():
response = call_API(
url=values["url"],
access_token=variables["access_token"]
)
api_data[category] = [response]
# initalize metadata with information for all api_data value keys
meta_api_data = {i:{j for j in api_data[i][0].keys()} for i in api_data.keys()}
[ ]:
meta_api_data
{'sleep': {'sleep'},
'steps': {'activities-steps'},
'minutesVeryActive': {'activities-minutesVeryActive'},
'minutesFairlyActive': {'activities-minutesFairlyActive'},
'minutesLightlyActive': {'activities-minutesLightlyActive'},
'distance': {'activities-distance'},
'minutesSedentary': {'activities-minutesSedentary'}}
[ ]:
api_data
{'sleep': [{'sleep': [{'dateOfSleep': '2022-05-24',
'duration': 25080000,
'efficiency': 95,
'endTime': '2022-05-24T07:27:00.000',
'infoCode': 0,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-24T00:28:30.000',
'level': 'wake',
'seconds': 30},
{'dateTime': '2022-05-24T00:29:00.000',
'level': 'light',
'seconds': 2400},
{'dateTime': '2022-05-24T01:09:00.000',
'level': 'rem',
'seconds': 1590},
{'dateTime': '2022-05-24T01:35:30.000',
'level': 'light',
'seconds': 4500},
{'dateTime': '2022-05-24T02:50:30.000', 'level': 'rem', 'seconds': 540},
{'dateTime': '2022-05-24T02:59:30.000',
'level': 'light',
'seconds': 420},
{'dateTime': '2022-05-24T03:06:30.000', 'level': 'rem', 'seconds': 300},
{'dateTime': '2022-05-24T03:11:30.000',
'level': 'light',
'seconds': 2970},
{'dateTime': '2022-05-24T04:01:00.000',
'level': 'wake',
'seconds': 210},
{'dateTime': '2022-05-24T04:04:30.000',
'level': 'light',
'seconds': 3930},
{'dateTime': '2022-05-24T05:10:00.000',
'level': 'wake',
'seconds': 1350},
{'dateTime': '2022-05-24T05:32:30.000',
'level': 'light',
'seconds': 30},
{'dateTime': '2022-05-24T05:33:00.000', 'level': 'rem', 'seconds': 420},
{'dateTime': '2022-05-24T05:40:00.000',
'level': 'light',
'seconds': 1110},
{'dateTime': '2022-05-24T05:58:30.000', 'level': 'rem', 'seconds': 270},
{'dateTime': '2022-05-24T06:03:00.000',
'level': 'light',
'seconds': 690},
{'dateTime': '2022-05-24T06:14:30.000',
'level': 'deep',
'seconds': 270},
{'dateTime': '2022-05-24T06:19:00.000',
'level': 'light',
'seconds': 540},
{'dateTime': '2022-05-24T06:28:00.000',
'level': 'deep',
'seconds': 1080},
{'dateTime': '2022-05-24T06:46:00.000',
'level': 'light',
'seconds': 330},
{'dateTime': '2022-05-24T06:51:30.000', 'level': 'rem', 'seconds': 690},
{'dateTime': '2022-05-24T07:03:00.000',
'level': 'light',
'seconds': 330},
{'dateTime': '2022-05-24T07:08:30.000', 'level': 'rem', 'seconds': 750},
{'dateTime': '2022-05-24T07:21:00.000',
'level': 'wake',
'seconds': 360}],
'shortData': [{'dateTime': '2022-05-24T01:08:00.000',
'level': 'wake',
'seconds': 30},
{'dateTime': '2022-05-24T01:34:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T01:36:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T01:42:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-24T01:45:30.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-24T02:08:30.000',
'level': 'wake',
'seconds': 180},
{'dateTime': '2022-05-24T02:19:00.000',
'level': 'wake',
'seconds': 180},
{'dateTime': '2022-05-24T02:24:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T02:39:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-24T02:44:00.000',
'level': 'wake',
'seconds': 120},
{'dateTime': '2022-05-24T02:49:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-24T03:11:30.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-24T03:38:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T03:40:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T03:43:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T04:08:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-24T04:19:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T04:26:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T04:36:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T04:48:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T04:56:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T05:00:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T05:40:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-24T06:44:00.000',
'level': 'wake',
'seconds': 120},
{'dateTime': '2022-05-24T06:47:30.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-24T07:02:30.000',
'level': 'wake',
'seconds': 30}],
'summary': {'deep': {'count': 2,
'minutes': 20,
'thirtyDayAvgMinutes': 52},
'light': {'count': 32, 'minutes': 261, 'thirtyDayAvgMinutes': 265},
'rem': {'count': 8, 'minutes': 75, 'thirtyDayAvgMinutes': 56},
'wake': {'count': 30, 'minutes': 62, 'thirtyDayAvgMinutes': 54}}},
'logId': 37012135078,
'logType': 'auto_detected',
'minutesAfterWakeup': 3,
'minutesAsleep': 356,
'minutesAwake': 62,
'minutesToFallAsleep': 0,
'startTime': '2022-05-24T00:28:30.000',
'timeInBed': 418,
'type': 'stages'},
{'dateOfSleep': '2022-05-23',
'duration': 23520000,
'efficiency': 99,
'endTime': '2022-05-23T06:33:00.000',
'infoCode': 0,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-23T00:00:30.000',
'level': 'wake',
'seconds': 30},
{'dateTime': '2022-05-23T00:01:00.000',
'level': 'light',
'seconds': 1260},
{'dateTime': '2022-05-23T00:22:00.000',
'level': 'deep',
'seconds': 270},
{'dateTime': '2022-05-23T00:26:30.000',
'level': 'light',
'seconds': 1920},
{'dateTime': '2022-05-23T00:58:30.000', 'level': 'rem', 'seconds': 450},
{'dateTime': '2022-05-23T01:06:00.000',
'level': 'light',
'seconds': 1140},
{'dateTime': '2022-05-23T01:25:00.000', 'level': 'rem', 'seconds': 840},
{'dateTime': '2022-05-23T01:39:00.000',
'level': 'light',
'seconds': 1470},
{'dateTime': '2022-05-23T02:03:30.000', 'level': 'rem', 'seconds': 270},
{'dateTime': '2022-05-23T02:08:00.000',
'level': 'light',
'seconds': 6510},
{'dateTime': '2022-05-23T03:56:30.000',
'level': 'wake',
'seconds': 540},
{'dateTime': '2022-05-23T04:05:30.000',
'level': 'light',
'seconds': 210},
{'dateTime': '2022-05-23T04:09:00.000',
'level': 'deep',
'seconds': 2220},
{'dateTime': '2022-05-23T04:46:00.000',
'level': 'light',
'seconds': 2100},
{'dateTime': '2022-05-23T05:21:00.000',
'level': 'wake',
'seconds': 300},
{'dateTime': '2022-05-23T05:26:00.000',
'level': 'light',
'seconds': 570},
{'dateTime': '2022-05-23T05:35:30.000',
'level': 'wake',
'seconds': 510},
{'dateTime': '2022-05-23T05:44:00.000',
'level': 'light',
'seconds': 960},
{'dateTime': '2022-05-23T06:00:00.000',
'level': 'deep',
'seconds': 1980}],
'shortData': [{'dateTime': '2022-05-23T00:00:30.000',
'level': 'wake',
'seconds': 30},
{'dateTime': '2022-05-23T00:03:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T00:06:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T00:10:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T01:36:30.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-23T01:41:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T01:59:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T02:08:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T02:13:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T02:20:30.000',
'level': 'wake',
'seconds': 120},
{'dateTime': '2022-05-23T02:29:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-23T02:37:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T02:51:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T02:53:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T03:00:30.000',
'level': 'wake',
'seconds': 120},
{'dateTime': '2022-05-23T03:12:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T03:20:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T03:35:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T04:45:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-23T04:48:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T04:50:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T05:13:30.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-23T05:17:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-23T05:56:00.000',
'level': 'wake',
'seconds': 150}],
'summary': {'deep': {'count': 3,
'minutes': 74,
'thirtyDayAvgMinutes': 49},
'light': {'count': 29, 'minutes': 252, 'thirtyDayAvgMinutes': 267},
'rem': {'count': 3, 'minutes': 23, 'thirtyDayAvgMinutes': 61},
'wake': {'count': 27, 'minutes': 43, 'thirtyDayAvgMinutes': 55}}},
'logId': 36997343818,
'logType': 'auto_detected',
'minutesAfterWakeup': 0,
'minutesAsleep': 349,
'minutesAwake': 43,
'minutesToFallAsleep': 0,
'startTime': '2022-05-23T00:00:30.000',
'timeInBed': 392,
'type': 'stages'},
{'dateOfSleep': '2022-05-22',
'duration': 26520000,
'efficiency': 96,
'endTime': '2022-05-22T06:55:30.000',
'infoCode': 0,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-21T23:33:30.000',
'level': 'wake',
'seconds': 270},
{'dateTime': '2022-05-21T23:38:00.000',
'level': 'light',
'seconds': 720},
{'dateTime': '2022-05-21T23:50:00.000',
'level': 'deep',
'seconds': 1410},
{'dateTime': '2022-05-22T00:13:30.000',
'level': 'light',
'seconds': 570},
{'dateTime': '2022-05-22T00:23:00.000', 'level': 'rem', 'seconds': 540},
{'dateTime': '2022-05-22T00:32:00.000',
'level': 'wake',
'seconds': 570},
{'dateTime': '2022-05-22T00:41:30.000',
'level': 'light',
'seconds': 3390},
{'dateTime': '2022-05-22T01:38:00.000', 'level': 'rem', 'seconds': 360},
{'dateTime': '2022-05-22T01:44:00.000',
'level': 'light',
'seconds': 210},
{'dateTime': '2022-05-22T01:47:30.000', 'level': 'rem', 'seconds': 450},
{'dateTime': '2022-05-22T01:55:00.000',
'level': 'light',
'seconds': 270},
{'dateTime': '2022-05-22T01:59:30.000',
'level': 'deep',
'seconds': 1170},
{'dateTime': '2022-05-22T02:19:00.000',
'level': 'light',
'seconds': 2190},
{'dateTime': '2022-05-22T02:55:30.000',
'level': 'deep',
'seconds': 330},
{'dateTime': '2022-05-22T03:01:00.000',
'level': 'light',
'seconds': 2430},
{'dateTime': '2022-05-22T03:41:30.000', 'level': 'rem', 'seconds': 270},
{'dateTime': '2022-05-22T03:46:00.000',
'level': 'light',
'seconds': 360},
{'dateTime': '2022-05-22T03:52:00.000',
'level': 'wake',
'seconds': 480},
{'dateTime': '2022-05-22T04:00:00.000',
'level': 'light',
'seconds': 2880},
{'dateTime': '2022-05-22T04:48:00.000', 'level': 'rem', 'seconds': 300},
{'dateTime': '2022-05-22T04:53:00.000',
'level': 'light',
'seconds': 480},
{'dateTime': '2022-05-22T05:01:00.000',
'level': 'rem',
'seconds': 1170},
{'dateTime': '2022-05-22T05:20:30.000',
'level': 'light',
'seconds': 840},
{'dateTime': '2022-05-22T05:34:30.000',
'level': 'wake',
'seconds': 330},
{'dateTime': '2022-05-22T05:40:00.000',
'level': 'light',
'seconds': 3930},
{'dateTime': '2022-05-22T06:45:30.000',
'level': 'wake',
'seconds': 600}],
'shortData': [{'dateTime': '2022-05-22T00:13:00.000',
'level': 'wake',
'seconds': 30},
{'dateTime': '2022-05-22T00:17:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T00:48:00.000',
'level': 'wake',
'seconds': 180},
{'dateTime': '2022-05-22T00:56:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T01:15:00.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-22T01:20:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T01:29:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-22T02:18:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T02:21:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T02:28:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T02:31:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T02:35:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-22T03:16:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T03:22:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T03:25:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T03:34:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-22T03:38:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-22T04:14:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-22T04:31:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T04:38:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T05:06:00.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-22T06:03:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-22T06:13:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-22T06:17:00.000',
'level': 'wake',
'seconds': 30}],
'summary': {'deep': {'count': 3,
'minutes': 48,
'thirtyDayAvgMinutes': 49},
'light': {'count': 33, 'minutes': 286, 'thirtyDayAvgMinutes': 264},
'rem': {'count': 7, 'minutes': 50, 'thirtyDayAvgMinutes': 63},
'wake': {'count': 29, 'minutes': 58, 'thirtyDayAvgMinutes': 55}}},
'logId': 36986231531,
'logType': 'auto_detected',
'minutesAfterWakeup': 0,
'minutesAsleep': 384,
'minutesAwake': 58,
'minutesToFallAsleep': 0,
'startTime': '2022-05-21T23:33:30.000',
'timeInBed': 442,
'type': 'stages'},
{'dateOfSleep': '2022-05-21',
'duration': 22440000,
'efficiency': 99,
'endTime': '2022-05-21T08:17:00.000',
'infoCode': 3,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-21T02:03:00.000',
'level': 'asleep',
'seconds': 1200},
{'dateTime': '2022-05-21T02:23:00.000',
'level': 'restless',
'seconds': 180},
{'dateTime': '2022-05-21T02:26:00.000',
'level': 'asleep',
'seconds': 16920},
{'dateTime': '2022-05-21T07:08:00.000',
'level': 'restless',
'seconds': 60},
{'dateTime': '2022-05-21T07:09:00.000',
'level': 'asleep',
'seconds': 1440},
{'dateTime': '2022-05-21T07:33:00.000',
'level': 'restless',
'seconds': 60},
{'dateTime': '2022-05-21T07:34:00.000',
'level': 'asleep',
'seconds': 2520},
{'dateTime': '2022-05-21T08:16:00.000',
'level': 'restless',
'seconds': 60}],
'summary': {'asleep': {'count': 0, 'minutes': 368},
'awake': {'count': 0, 'minutes': 0},
'restless': {'count': 4, 'minutes': 6}}},
'logId': 36974957200,
'logType': 'auto_detected',
'minutesAfterWakeup': 1,
'minutesAsleep': 368,
'minutesAwake': 5,
'minutesToFallAsleep': 0,
'startTime': '2022-05-21T02:03:00.000',
'timeInBed': 374,
'type': 'classic'},
{'dateOfSleep': '2022-05-20',
'duration': 26340000,
'efficiency': 99,
'endTime': '2022-05-20T06:59:00.000',
'infoCode': 0,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-19T23:39:30.000',
'level': 'wake',
'seconds': 60},
{'dateTime': '2022-05-19T23:40:30.000',
'level': 'light',
'seconds': 2700},
{'dateTime': '2022-05-20T00:25:30.000',
'level': 'deep',
'seconds': 330},
{'dateTime': '2022-05-20T00:31:00.000',
'level': 'light',
'seconds': 2250},
{'dateTime': '2022-05-20T01:08:30.000',
'level': 'wake',
'seconds': 210},
{'dateTime': '2022-05-20T01:12:00.000',
'level': 'light',
'seconds': 1440},
{'dateTime': '2022-05-20T01:36:00.000', 'level': 'rem', 'seconds': 300},
{'dateTime': '2022-05-20T01:41:00.000',
'level': 'light',
'seconds': 1170},
{'dateTime': '2022-05-20T02:00:30.000',
'level': 'wake',
'seconds': 390},
{'dateTime': '2022-05-20T02:07:00.000',
'level': 'light',
'seconds': 1290},
{'dateTime': '2022-05-20T02:28:30.000',
'level': 'deep',
'seconds': 330},
{'dateTime': '2022-05-20T02:34:00.000',
'level': 'light',
'seconds': 3690},
{'dateTime': '2022-05-20T03:35:30.000', 'level': 'rem', 'seconds': 930},
{'dateTime': '2022-05-20T03:51:00.000',
'level': 'light',
'seconds': 480},
{'dateTime': '2022-05-20T03:59:00.000',
'level': 'rem',
'seconds': 1080},
{'dateTime': '2022-05-20T04:17:00.000',
'level': 'light',
'seconds': 2040},
{'dateTime': '2022-05-20T04:51:00.000',
'level': 'deep',
'seconds': 2010},
{'dateTime': '2022-05-20T05:24:30.000',
'level': 'wake',
'seconds': 270},
{'dateTime': '2022-05-20T05:29:00.000',
'level': 'light',
'seconds': 1710},
{'dateTime': '2022-05-20T05:57:30.000',
'level': 'wake',
'seconds': 420},
{'dateTime': '2022-05-20T06:04:30.000',
'level': 'light',
'seconds': 510},
{'dateTime': '2022-05-20T06:13:00.000', 'level': 'rem', 'seconds': 540},
{'dateTime': '2022-05-20T06:22:00.000',
'level': 'light',
'seconds': 1320},
{'dateTime': '2022-05-20T06:44:00.000',
'level': 'wake',
'seconds': 510},
{'dateTime': '2022-05-20T06:52:30.000',
'level': 'light',
'seconds': 180},
{'dateTime': '2022-05-20T06:55:30.000',
'level': 'wake',
'seconds': 210}],
'shortData': [{'dateTime': '2022-05-19T23:39:30.000',
'level': 'wake',
'seconds': 60},
{'dateTime': '2022-05-19T23:59:30.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-20T00:31:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-20T00:40:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-20T02:36:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-20T02:38:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-20T02:40:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-20T02:53:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-20T03:04:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-20T03:10:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-20T03:26:00.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-20T04:16:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-20T04:30:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-20T05:42:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-20T06:24:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-20T06:38:30.000',
'level': 'wake',
'seconds': 30}],
'summary': {'deep': {'count': 3,
'minutes': 45,
'thirtyDayAvgMinutes': 50},
'light': {'count': 25, 'minutes': 301, 'thirtyDayAvgMinutes': 257},
'rem': {'count': 4, 'minutes': 46, 'thirtyDayAvgMinutes': 66},
'wake': {'count': 22, 'minutes': 47, 'thirtyDayAvgMinutes': 57}}},
'logId': 36962644425,
'logType': 'auto_detected',
'minutesAfterWakeup': 0,
'minutesAsleep': 392,
'minutesAwake': 47,
'minutesToFallAsleep': 0,
'startTime': '2022-05-19T23:39:30.000',
'timeInBed': 439,
'type': 'stages'},
{'dateOfSleep': '2022-05-19',
'duration': 4500000,
'efficiency': 90,
'endTime': '2022-05-19T18:16:00.000',
'infoCode': 2,
'isMainSleep': False,
'levels': {'data': [{'dateTime': '2022-05-19T17:01:00.000',
'level': 'awake',
'seconds': 60},
{'dateTime': '2022-05-19T17:02:00.000',
'level': 'restless',
'seconds': 60},
{'dateTime': '2022-05-19T17:03:00.000',
'level': 'asleep',
'seconds': 720},
{'dateTime': '2022-05-19T17:15:00.000',
'level': 'restless',
'seconds': 60},
{'dateTime': '2022-05-19T17:16:00.000',
'level': 'asleep',
'seconds': 2040},
{'dateTime': '2022-05-19T17:50:00.000',
'level': 'restless',
'seconds': 240},
{'dateTime': '2022-05-19T17:54:00.000',
'level': 'asleep',
'seconds': 840},
{'dateTime': '2022-05-19T18:08:00.000',
'level': 'restless',
'seconds': 480}],
'summary': {'asleep': {'count': 0, 'minutes': 60},
'awake': {'count': 1, 'minutes': 1},
'restless': {'count': 4, 'minutes': 14}}},
'logId': 36952035144,
'logType': 'auto_detected',
'minutesAfterWakeup': 8,
'minutesAsleep': 60,
'minutesAwake': 7,
'minutesToFallAsleep': 0,
'startTime': '2022-05-19T17:01:00.000',
'timeInBed': 75,
'type': 'classic'},
{'dateOfSleep': '2022-05-19',
'duration': 20460000,
'efficiency': 97,
'endTime': '2022-05-19T05:50:30.000',
'infoCode': 0,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-19T00:09:30.000',
'level': 'light',
'seconds': 540},
{'dateTime': '2022-05-19T00:18:30.000',
'level': 'deep',
'seconds': 2910},
{'dateTime': '2022-05-19T01:07:00.000',
'level': 'wake',
'seconds': 780},
{'dateTime': '2022-05-19T01:20:00.000',
'level': 'light',
'seconds': 420},
{'dateTime': '2022-05-19T01:27:00.000',
'level': 'wake',
'seconds': 600},
{'dateTime': '2022-05-19T01:37:00.000',
'level': 'light',
'seconds': 3270},
{'dateTime': '2022-05-19T02:31:30.000', 'level': 'rem', 'seconds': 510},
{'dateTime': '2022-05-19T02:40:00.000',
'level': 'light',
'seconds': 30},
{'dateTime': '2022-05-19T02:40:30.000',
'level': 'wake',
'seconds': 540},
{'dateTime': '2022-05-19T02:49:30.000',
'level': 'light',
'seconds': 300},
{'dateTime': '2022-05-19T02:54:30.000',
'level': 'wake',
'seconds': 510},
{'dateTime': '2022-05-19T03:03:00.000',
'level': 'light',
'seconds': 2730},
{'dateTime': '2022-05-19T03:48:30.000',
'level': 'wake',
'seconds': 210},
{'dateTime': '2022-05-19T03:52:00.000',
'level': 'light',
'seconds': 900},
{'dateTime': '2022-05-19T04:07:00.000', 'level': 'rem', 'seconds': 330},
{'dateTime': '2022-05-19T04:12:30.000',
'level': 'light',
'seconds': 1500},
{'dateTime': '2022-05-19T04:37:30.000',
'level': 'rem',
'seconds': 1740},
{'dateTime': '2022-05-19T05:06:30.000',
'level': 'light',
'seconds': 1440},
{'dateTime': '2022-05-19T05:30:30.000',
'level': 'wake',
'seconds': 390},
{'dateTime': '2022-05-19T05:37:00.000',
'level': 'light',
'seconds': 120},
{'dateTime': '2022-05-19T05:39:00.000', 'level': 'rem', 'seconds': 330},
{'dateTime': '2022-05-19T05:44:30.000',
'level': 'wake',
'seconds': 360}],
'shortData': [{'dateTime': '2022-05-19T01:41:30.000',
'level': 'wake',
'seconds': 30},
{'dateTime': '2022-05-19T02:07:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-19T03:29:00.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-19T04:12:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-19T04:15:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-19T04:35:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-19T04:39:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-19T05:08:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-19T05:15:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-19T05:39:00.000',
'level': 'wake',
'seconds': 60}],
'summary': {'deep': {'count': 1,
'minutes': 48,
'thirtyDayAvgMinutes': 50},
'light': {'count': 17, 'minutes': 182, 'thirtyDayAvgMinutes': 276},
'rem': {'count': 5, 'minutes': 47, 'thirtyDayAvgMinutes': 71},
'wake': {'count': 17, 'minutes': 64, 'thirtyDayAvgMinutes': 55}}},
'logId': 36948394201,
'logType': 'auto_detected',
'minutesAfterWakeup': 1,
'minutesAsleep': 277,
'minutesAwake': 64,
'minutesToFallAsleep': 0,
'startTime': '2022-05-19T00:09:30.000',
'timeInBed': 341,
'type': 'stages'},
{'dateOfSleep': '2022-05-18',
'duration': 27480000,
'efficiency': 96,
'endTime': '2022-05-18T07:36:00.000',
'infoCode': 0,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-17T23:57:30.000',
'level': 'light',
'seconds': 180},
{'dateTime': '2022-05-18T00:00:30.000',
'level': 'wake',
'seconds': 930},
{'dateTime': '2022-05-18T00:16:00.000',
'level': 'light',
'seconds': 1350},
{'dateTime': '2022-05-18T00:38:30.000',
'level': 'deep',
'seconds': 270},
{'dateTime': '2022-05-18T00:43:00.000',
'level': 'light',
'seconds': 330},
{'dateTime': '2022-05-18T00:48:30.000', 'level': 'rem', 'seconds': 270},
{'dateTime': '2022-05-18T00:53:00.000',
'level': 'deep',
'seconds': 330},
{'dateTime': '2022-05-18T00:58:30.000',
'level': 'light',
'seconds': 5970},
{'dateTime': '2022-05-18T02:38:00.000',
'level': 'wake',
'seconds': 210},
{'dateTime': '2022-05-18T02:41:30.000',
'level': 'light',
'seconds': 3870},
{'dateTime': '2022-05-18T03:46:00.000', 'level': 'rem', 'seconds': 270},
{'dateTime': '2022-05-18T03:50:30.000',
'level': 'light',
'seconds': 1230},
{'dateTime': '2022-05-18T04:11:00.000', 'level': 'rem', 'seconds': 330},
{'dateTime': '2022-05-18T04:16:30.000',
'level': 'light',
'seconds': 4800},
{'dateTime': '2022-05-18T05:36:30.000', 'level': 'rem', 'seconds': 960},
{'dateTime': '2022-05-18T05:52:30.000',
'level': 'light',
'seconds': 90},
{'dateTime': '2022-05-18T05:54:00.000',
'level': 'deep',
'seconds': 540},
{'dateTime': '2022-05-18T06:03:00.000',
'level': 'light',
'seconds': 630},
{'dateTime': '2022-05-18T06:13:30.000',
'level': 'deep',
'seconds': 300},
{'dateTime': '2022-05-18T06:18:30.000',
'level': 'light',
'seconds': 90},
{'dateTime': '2022-05-18T06:20:00.000',
'level': 'wake',
'seconds': 450},
{'dateTime': '2022-05-18T06:27:30.000',
'level': 'light',
'seconds': 1320},
{'dateTime': '2022-05-18T06:49:30.000',
'level': 'deep',
'seconds': 1290},
{'dateTime': '2022-05-18T07:11:00.000',
'level': 'light',
'seconds': 570},
{'dateTime': '2022-05-18T07:20:30.000', 'level': 'rem', 'seconds': 330},
{'dateTime': '2022-05-18T07:26:00.000',
'level': 'light',
'seconds': 600}],
'shortData': [{'dateTime': '2022-05-17T23:58:00.000',
'level': 'wake',
'seconds': 30},
{'dateTime': '2022-05-18T00:59:00.000',
'level': 'wake',
'seconds': 120},
{'dateTime': '2022-05-18T01:04:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T01:17:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T01:50:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T01:53:30.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-18T02:12:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-18T02:20:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T02:49:00.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-18T02:57:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-18T03:20:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T03:22:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T03:27:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T03:54:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-18T04:16:30.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-18T04:32:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T04:38:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T04:43:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T04:58:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T05:02:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-18T05:06:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-18T05:18:00.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-18T05:23:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-18T05:32:30.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-18T05:44:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T06:07:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-18T06:37:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T07:10:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-18T07:13:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-18T07:26:00.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-18T07:35:30.000',
'level': 'wake',
'seconds': 30}],
'summary': {'deep': {'count': 5,
'minutes': 45,
'thirtyDayAvgMinutes': 52},
'light': {'count': 39, 'minutes': 322, 'thirtyDayAvgMinutes': 260},
'rem': {'count': 6, 'minutes': 35, 'thirtyDayAvgMinutes': 83},
'wake': {'count': 34, 'minutes': 56, 'thirtyDayAvgMinutes': 54}}},
'logId': 36934687631,
'logType': 'auto_detected',
'minutesAfterWakeup': 0,
'minutesAsleep': 402,
'minutesAwake': 56,
'minutesToFallAsleep': 0,
'startTime': '2022-05-17T23:57:30.000',
'timeInBed': 458,
'type': 'stages'},
{'dateOfSleep': '2022-05-17',
'duration': 25560000,
'efficiency': 97,
'endTime': '2022-05-17T07:24:30.000',
'infoCode': 0,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-17T00:18:30.000',
'level': 'wake',
'seconds': 1470},
{'dateTime': '2022-05-17T00:43:00.000',
'level': 'light',
'seconds': 810},
{'dateTime': '2022-05-17T00:56:30.000',
'level': 'deep',
'seconds': 1410},
{'dateTime': '2022-05-17T01:20:00.000',
'level': 'light',
'seconds': 120},
{'dateTime': '2022-05-17T01:22:00.000',
'level': 'rem',
'seconds': 2640},
{'dateTime': '2022-05-17T02:06:00.000',
'level': 'light',
'seconds': 840},
{'dateTime': '2022-05-17T02:20:00.000',
'level': 'deep',
'seconds': 1800},
{'dateTime': '2022-05-17T02:50:00.000',
'level': 'light',
'seconds': 6750},
{'dateTime': '2022-05-17T04:42:30.000',
'level': 'wake',
'seconds': 240},
{'dateTime': '2022-05-17T04:46:30.000',
'level': 'light',
'seconds': 30},
{'dateTime': '2022-05-17T04:47:00.000', 'level': 'rem', 'seconds': 540},
{'dateTime': '2022-05-17T04:56:00.000',
'level': 'light',
'seconds': 2580},
{'dateTime': '2022-05-17T05:39:00.000', 'level': 'rem', 'seconds': 480},
{'dateTime': '2022-05-17T05:47:00.000',
'level': 'light',
'seconds': 360},
{'dateTime': '2022-05-17T05:53:00.000',
'level': 'rem',
'seconds': 1140},
{'dateTime': '2022-05-17T06:12:00.000',
'level': 'light',
'seconds': 540},
{'dateTime': '2022-05-17T06:21:00.000',
'level': 'rem',
'seconds': 1050},
{'dateTime': '2022-05-17T06:38:30.000',
'level': 'light',
'seconds': 2160},
{'dateTime': '2022-05-17T07:14:30.000', 'level': 'rem', 'seconds': 510},
{'dateTime': '2022-05-17T07:23:00.000',
'level': 'light',
'seconds': 90}],
'shortData': [{'dateTime': '2022-05-17T01:18:00.000',
'level': 'wake',
'seconds': 120},
{'dateTime': '2022-05-17T02:49:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-17T03:09:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-17T03:13:00.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-17T03:48:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-17T03:52:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-17T03:54:30.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-17T04:14:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-17T04:24:30.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-17T04:30:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-17T05:00:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-17T05:05:00.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-17T05:09:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-17T05:49:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-17T06:03:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-17T06:09:30.000',
'level': 'wake',
'seconds': 150},
{'dateTime': '2022-05-17T06:48:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-17T06:50:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-17T06:54:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-17T06:59:00.000',
'level': 'wake',
'seconds': 150}],
'summary': {'deep': {'count': 2,
'minutes': 51,
'thirtyDayAvgMinutes': 53},
'light': {'count': 26, 'minutes': 217, 'thirtyDayAvgMinutes': 282},
'rem': {'count': 7, 'minutes': 103, 'thirtyDayAvgMinutes': 74},
'wake': {'count': 22, 'minutes': 55, 'thirtyDayAvgMinutes': 54}}},
'logId': 36920846728,
'logType': 'auto_detected',
'minutesAfterWakeup': 0,
'minutesAsleep': 371,
'minutesAwake': 55,
'minutesToFallAsleep': 0,
'startTime': '2022-05-17T00:18:30.000',
'timeInBed': 426,
'type': 'stages'},
{'dateOfSleep': '2022-05-16',
'duration': 28560000,
'efficiency': 99,
'endTime': '2022-05-16T07:59:30.000',
'infoCode': 0,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-16T00:03:30.000',
'level': 'wake',
'seconds': 1170},
{'dateTime': '2022-05-16T00:23:00.000',
'level': 'light',
'seconds': 420},
{'dateTime': '2022-05-16T00:30:00.000',
'level': 'wake',
'seconds': 420},
{'dateTime': '2022-05-16T00:37:00.000', 'level': 'rem', 'seconds': 480},
{'dateTime': '2022-05-16T00:45:00.000',
'level': 'wake',
'seconds': 600},
{'dateTime': '2022-05-16T00:55:00.000',
'level': 'light',
'seconds': 990},
{'dateTime': '2022-05-16T01:11:30.000',
'level': 'deep',
'seconds': 1020},
{'dateTime': '2022-05-16T01:28:30.000',
'level': 'rem',
'seconds': 1260},
{'dateTime': '2022-05-16T01:49:30.000',
'level': 'wake',
'seconds': 330},
{'dateTime': '2022-05-16T01:55:00.000',
'level': 'light',
'seconds': 3720},
{'dateTime': '2022-05-16T02:57:00.000', 'level': 'rem', 'seconds': 420},
{'dateTime': '2022-05-16T03:04:00.000',
'level': 'light',
'seconds': 210},
{'dateTime': '2022-05-16T03:07:30.000', 'level': 'rem', 'seconds': 840},
{'dateTime': '2022-05-16T03:21:30.000',
'level': 'light',
'seconds': 3480},
{'dateTime': '2022-05-16T04:19:30.000',
'level': 'rem',
'seconds': 1800},
{'dateTime': '2022-05-16T04:49:30.000',
'level': 'light',
'seconds': 1500},
{'dateTime': '2022-05-16T05:14:30.000',
'level': 'deep',
'seconds': 720},
{'dateTime': '2022-05-16T05:26:30.000',
'level': 'light',
'seconds': 2190},
{'dateTime': '2022-05-16T06:03:00.000', 'level': 'rem', 'seconds': 450},
{'dateTime': '2022-05-16T06:10:30.000',
'level': 'light',
'seconds': 450},
{'dateTime': '2022-05-16T06:18:00.000', 'level': 'rem', 'seconds': 360},
{'dateTime': '2022-05-16T06:24:00.000',
'level': 'light',
'seconds': 960},
{'dateTime': '2022-05-16T06:40:00.000',
'level': 'wake',
'seconds': 210},
{'dateTime': '2022-05-16T06:43:30.000',
'level': 'light',
'seconds': 4290},
{'dateTime': '2022-05-16T07:55:00.000',
'level': 'wake',
'seconds': 270}],
'shortData': [{'dateTime': '2022-05-16T02:30:30.000',
'level': 'wake',
'seconds': 30},
{'dateTime': '2022-05-16T02:34:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T02:41:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T02:45:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T03:09:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T03:23:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T03:25:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T03:41:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T03:55:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T04:08:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-16T04:33:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T04:46:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T04:51:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T05:48:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T05:52:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T05:54:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-16T06:11:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T06:52:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T06:54:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-16T07:21:30.000',
'level': 'wake',
'seconds': 120},
{'dateTime': '2022-05-16T07:32:00.000',
'level': 'wake',
'seconds': 60}],
'summary': {'deep': {'count': 2,
'minutes': 29,
'thirtyDayAvgMinutes': 76},
'light': {'count': 28, 'minutes': 292, 'thirtyDayAvgMinutes': 271},
'rem': {'count': 10, 'minutes': 92, 'thirtyDayAvgMinutes': 55},
'wake': {'count': 27, 'minutes': 63, 'thirtyDayAvgMinutes': 45}}},
'logId': 36908354898,
'logType': 'auto_detected',
'minutesAfterWakeup': 1,
'minutesAsleep': 413,
'minutesAwake': 63,
'minutesToFallAsleep': 0,
'startTime': '2022-05-16T00:03:30.000',
'timeInBed': 476,
'type': 'stages'},
{'dateOfSleep': '2022-05-15',
'duration': 26820000,
'efficiency': 96,
'endTime': '2022-05-15T07:33:00.000',
'infoCode': 0,
'isMainSleep': True,
'levels': {'data': [{'dateTime': '2022-05-15T00:05:30.000',
'level': 'wake',
'seconds': 30},
{'dateTime': '2022-05-15T00:06:00.000',
'level': 'deep',
'seconds': 1170},
{'dateTime': '2022-05-15T00:25:30.000',
'level': 'light',
'seconds': 480},
{'dateTime': '2022-05-15T00:33:30.000',
'level': 'deep',
'seconds': 1080},
{'dateTime': '2022-05-15T00:51:30.000',
'level': 'light',
'seconds': 480},
{'dateTime': '2022-05-15T00:59:30.000',
'level': 'deep',
'seconds': 1380},
{'dateTime': '2022-05-15T01:22:30.000',
'level': 'wake',
'seconds': 420},
{'dateTime': '2022-05-15T01:29:30.000',
'level': 'light',
'seconds': 2460},
{'dateTime': '2022-05-15T02:10:30.000',
'level': 'wake',
'seconds': 270},
{'dateTime': '2022-05-15T02:15:00.000',
'level': 'light',
'seconds': 5220},
{'dateTime': '2022-05-15T03:42:00.000',
'level': 'deep',
'seconds': 900},
{'dateTime': '2022-05-15T03:57:00.000',
'level': 'light',
'seconds': 2760},
{'dateTime': '2022-05-15T04:43:00.000',
'level': 'rem',
'seconds': 2940},
{'dateTime': '2022-05-15T05:32:00.000',
'level': 'light',
'seconds': 2340},
{'dateTime': '2022-05-15T06:11:00.000', 'level': 'rem', 'seconds': 480},
{'dateTime': '2022-05-15T06:19:00.000',
'level': 'light',
'seconds': 990},
{'dateTime': '2022-05-15T06:35:30.000',
'level': 'deep',
'seconds': 300},
{'dateTime': '2022-05-15T06:40:30.000',
'level': 'wake',
'seconds': 360},
{'dateTime': '2022-05-15T06:46:30.000',
'level': 'light',
'seconds': 2520},
{'dateTime': '2022-05-15T07:28:30.000',
'level': 'wake',
'seconds': 270}],
'shortData': [{'dateTime': '2022-05-15T00:50:00.000',
'level': 'wake',
'seconds': 90},
{'dateTime': '2022-05-15T00:57:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T01:48:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T01:52:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T01:54:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T01:57:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-15T02:00:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T02:08:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T02:35:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T03:01:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-15T03:17:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-15T03:56:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T04:07:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-15T04:29:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T04:37:00.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-15T04:40:00.000',
'level': 'wake',
'seconds': 120},
{'dateTime': '2022-05-15T05:11:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T05:23:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T05:27:00.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T05:32:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T06:05:00.000', 'level': 'wake', 'seconds': 90},
{'dateTime': '2022-05-15T07:00:30.000', 'level': 'wake', 'seconds': 60},
{'dateTime': '2022-05-15T07:04:30.000', 'level': 'wake', 'seconds': 30},
{'dateTime': '2022-05-15T07:15:00.000',
'level': 'wake',
'seconds': 90}],
'summary': {'deep': {'count': 5,
'minutes': 76,
'thirtyDayAvgMinutes': 0},
'light': {'count': 27, 'minutes': 271, 'thirtyDayAvgMinutes': 0},
'rem': {'count': 5, 'minutes': 55, 'thirtyDayAvgMinutes': 0},
'wake': {'count': 29, 'minutes': 45, 'thirtyDayAvgMinutes': 0}}},
'logId': 36894637024,
'logType': 'auto_detected',
'minutesAfterWakeup': 0,
'minutesAsleep': 402,
'minutesAwake': 45,
'minutesToFallAsleep': 0,
'startTime': '2022-05-15T00:05:30.000',
'timeInBed': 447,
'type': 'stages'}]}],
'steps': [{'activities-steps': [{'dateTime': '2022-05-15', 'value': '11046'},
{'dateTime': '2022-05-16', 'value': '5001'},
{'dateTime': '2022-05-17', 'value': '7698'},
{'dateTime': '2022-05-18', 'value': '9509'},
{'dateTime': '2022-05-19', 'value': '6070'},
{'dateTime': '2022-05-20', 'value': '3250'},
{'dateTime': '2022-05-21', 'value': '8640'},
{'dateTime': '2022-05-22', 'value': '11846'},
{'dateTime': '2022-05-23', 'value': '7513'},
{'dateTime': '2022-05-24', 'value': '8833'},
{'dateTime': '2022-05-25', 'value': '0'}]}],
'minutesVeryActive': [{'activities-minutesVeryActive': [{'dateTime': '2022-05-15',
'value': '39'},
{'dateTime': '2022-05-16', 'value': '42'},
{'dateTime': '2022-05-17', 'value': '76'},
{'dateTime': '2022-05-18', 'value': '17'},
{'dateTime': '2022-05-19', 'value': '5'},
{'dateTime': '2022-05-20', 'value': '20'},
{'dateTime': '2022-05-21', 'value': '3'},
{'dateTime': '2022-05-22', 'value': '6'},
{'dateTime': '2022-05-23', 'value': '52'},
{'dateTime': '2022-05-24', 'value': '26'},
{'dateTime': '2022-05-25', 'value': '0'}]}],
'minutesFairlyActive': [{'activities-minutesFairlyActive': [{'dateTime': '2022-05-15',
'value': '234'},
{'dateTime': '2022-05-16', 'value': '20'},
{'dateTime': '2022-05-17', 'value': '31'},
{'dateTime': '2022-05-18', 'value': '226'},
{'dateTime': '2022-05-19', 'value': '187'},
{'dateTime': '2022-05-20', 'value': '311'},
{'dateTime': '2022-05-21', 'value': '255'},
{'dateTime': '2022-05-22', 'value': '235'},
{'dateTime': '2022-05-23', 'value': '39'},
{'dateTime': '2022-05-24', 'value': '57'},
{'dateTime': '2022-05-25', 'value': '0'}]}],
'minutesLightlyActive': [{'activities-minutesLightlyActive': [{'dateTime': '2022-05-15',
'value': '156'},
{'dateTime': '2022-05-16', 'value': '148'},
{'dateTime': '2022-05-17', 'value': '185'},
{'dateTime': '2022-05-18', 'value': '129'},
{'dateTime': '2022-05-19', 'value': '134'},
{'dateTime': '2022-05-20', 'value': '119'},
{'dateTime': '2022-05-21', 'value': '136'},
{'dateTime': '2022-05-22', 'value': '185'},
{'dateTime': '2022-05-23', 'value': '210'},
{'dateTime': '2022-05-24', 'value': '245'},
{'dateTime': '2022-05-25', 'value': '0'}]}],
'distance': [{'activities-distance': [{'dateTime': '2022-05-15',
'value': '7.59607'},
{'dateTime': '2022-05-16', 'value': '3.90482'},
{'dateTime': '2022-05-17', 'value': '6.077109999999999'},
{'dateTime': '2022-05-18', 'value': '5.67516'},
{'dateTime': '2022-05-19', 'value': '4.71543'},
{'dateTime': '2022-05-20', 'value': '2.5508599999999997'},
{'dateTime': '2022-05-21', 'value': '6.79878'},
{'dateTime': '2022-05-22', 'value': '8.044559999999999'},
{'dateTime': '2022-05-23', 'value': '5.9309899999999995'},
{'dateTime': '2022-05-24', 'value': '5.96207'},
{'dateTime': '2022-05-25', 'value': '0.0'}]}],
'minutesSedentary': [{'activities-minutesSedentary': [{'dateTime': '2022-05-15',
'value': '564'},
{'dateTime': '2022-05-16', 'value': '754'},
{'dateTime': '2022-05-17', 'value': '722'},
{'dateTime': '2022-05-18', 'value': '610'},
{'dateTime': '2022-05-19', 'value': '698'},
{'dateTime': '2022-05-20', 'value': '551'},
{'dateTime': '2022-05-21', 'value': '672'},
{'dateTime': '2022-05-22', 'value': '572'},
{'dateTime': '2022-05-23', 'value': '747'},
{'dateTime': '2022-05-24', 'value': '694'},
{'dateTime': '2022-05-25', 'value': '1440'}]}]}
4. Data visualization§
4.1 Visualizing Non-Wear Days and Filtering The Data§
Here we are going to visualize what days data wasn’t collected. Since the days in which data wasn’t collected show “zero” values for the data, then filter this out to increase the accuracy of the analysis. This is because zero values will be consided data points in the analysis which will alter the results.
[ ]:
# First we are going to aggregate the data in arrays. We are taking distance as an example here
dates = []
distances = []
for datapoint in api_data['distance'][0]['activities-distance']:
dates.append(datapoint['dateTime'])
distances.append(float(datapoint['value']))
#Using a pandas dataframe to aggregate the data
d = {'Distance (km)': distances, 'Date': dates}
df = pd.DataFrame(data=d)
#Creating the plot
sns.set_theme(style="dark")
ax = sns.barplot(x="Distance (km)", y="Date", data=df, palette = 'Dark2_r' )
From the graph you can see the day(s) which have the distance as zero. These are the non-wear days and now we are creating a simple function to filter them out.
[ ]:
#since the data will consist of multiple arrays carrying multiple data points,
# we will create the function such that it gets the list of arrays as a parameter
# and the refrence index for the array to examine the data from
def remove_non_wear(lst, refrence_index):
newlst = []
for i in range(len(lst)):
newlst.append([])
for index in range(len(lst[refrence_index])):
if lst[refrence_index][index] != 0:
for array in lst:
newlst[lst.index(array)].append(array[index])
return newlst
[ ]:
#Now let's test this
new_arrays = remove_non_wear([dates, distances], 1)
#creating a new plot with the new data
dates = new_arrays[0]
distances = new_arrays[1]
#Using a pandas dataframe to aggregate the data
d = {'Distance (km)': distances, 'Date': dates}
df = pd.DataFrame(data=d)
#Creating the plot
sns.set_theme(style="dark")
ax = sns.barplot(x="Distance (km)", y="Date", data=df, palette = 'Dark2')
Looked like we could filter out the non-wear days!
4.2 Visualizing Steps§
Here we are going to try replicating this plot from the fitbit app. First, we gather the data in arrays, select the data for the desired days then we create a bar plot. Afterwards, we format the bar graph to like the original graph and place the labels.
[ ]:
# First gather the data in arrays
dates = []
steps = []
for datapoint in api_data['steps'][0]['activities-steps']:
dates.append(datapoint['dateTime'])
steps.append(float(datapoint['value']))
# We need only 7 days' worth of data so we slice the arrays
dates = dates[0:7]
steps = steps[0:7]
with plt.style.context('dark_background'):
#Creating the plot
fig,ax = plt.subplots()
fig.set_size_inches(3,5.5)
plt.bar(dates, steps, color = '#80a9ab', edgecolor = '#80a9ab')
fig.patch.set_facecolor('#02575c')
plt.gca().set_facecolor('#02575c')
#Adjusting the labels
thesteps = [0, 5000, 10000, 15000, 20000]
plt.yticks(ticks=thesteps, labels=['0', '5k', '10k','15k', '20k'])
plt.xticks(ticks=dates, labels=['S', 'M', 'T','W','T', 'F', 'S'])
# removing the borders from four sides
plt.gca().spines['left'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['bottom'].set_visible(False)
# adjust tick sizes
plt.tick_params(axis='x', labelsize=8)
plt.tick_params(axis='y', labelsize=8)
#adding labels
average_steps = sum(steps) / len(steps)
plt.figtext(0.5,1.0, str(int(average_steps))[0] + ',' + str(int(average_steps))[1:4] + " steps", fontsize=14, ha='center', color ='w', fontweight = 'bold')
plt.figtext(0.5,0.96, 'Average May 15 - 21', fontsize=10, ha='center', color ='w', fontweight = 'light')
# Creating a horizontal line at 10k steps
plt.axhline(y=10000, linewidth = 0.5)
Now, it looks very similar to the original Plot!
4.3 Visualizing Sleep Stages§
Here we are trying to replicate the sleep stages visual from the fitbit app. It’s very similar to the previous plot but with some extra edits, and adding the legend. Like the previous one, we are collecting 7 days’ sleep data and plotting them in a bar graph.
[ ]:
# first we collect the data in arrays
asleep = []
dates_sleep = []
light = []
rem = []
deep = []
no_stages = []
for datapoint in api_data['sleep'][0]['sleep']:
asleep.append(float(datapoint['minutesAsleep']))
dates_sleep.append(datapoint['dateOfSleep'])
if "rem" in datapoint['levels']['summary']:
rem.append(float(datapoint['levels']['summary']['rem']['minutes']))
else:
rem.append(0)
if "light" in datapoint['levels']['summary']:
light.append(float(datapoint['levels']['summary']['light']['minutes']))
else:
light.append(0)
if "deep" in datapoint['levels']['summary']:
deep.append(float(datapoint['levels']['summary']['deep']['minutes']))
else:
deep.append(0)
#fill the no_stages data
for i in range(len(asleep)):
no_stages.append(asleep[i]-(rem[i]+deep[i]+light[i]))
#get the ordered dates from the steps data
dates = []
for datapoint in api_data['steps'][0]['activities-steps']:
dates.append(datapoint['dateTime'])
dates = dates[0:7]
#adjust the order of the dates and take the needed 7 days
# also we are adding major and minor sleep values which appear as different
#entries with the same date
light_new = [0]*len(dates)
rem_new = [0]*len(dates)
deep_new = [0]*len(dates)
no_stages_new = [0]*len(dates)
asleep_new = [0]*len(dates)
for i,entry in enumerate(dates):
for j, date in enumerate(dates_sleep):
if date == entry:
light_new[i] += light[j]
rem_new[i] += rem[j]
deep_new[i]+= deep[j]
no_stages_new[i] += no_stages[j]
asleep_new[i] += asleep[j]
#Now plot the graph
colors = ['#81c4f9', '#418cff', '#184ba5', '#505071']
bottom1 = []
for i in range(len(deep_new)):
bottom1.append(deep_new[i]+light_new[i])
bottom2 = []
for i in range(len(deep_new)):
bottom2.append(rem_new[i]+bottom1[i])
with plt.style.context('dark_background'):
#adjust size
fig,ax = plt.subplots()
fig.set_size_inches(4,7)
plt.bar(dates, deep_new, color=colors[2], edgecolor = colors[2], width = 0.5)
plt.bar(dates, light_new, bottom = deep_new, color=colors[1], edgecolor = colors[1], width = 0.5)
plt.bar(dates, rem_new, bottom = bottom1, color=colors[0], edgecolor = colors[0], width = 0.5)
plt.bar(dates, no_stages_new, bottom = bottom2, color=colors[3], edgecolor = colors[3], width = 0.5)
#adjust colors
fig.patch.set_facecolor('#22204e')
plt.gca().set_facecolor('#22204e')
#Adjusting the labels
thesteps = [0, 60, 2*60, 3*60, 4*60, 5*60, 6*60, 7*60, 8*60]
plt.yticks(ticks=thesteps, labels=['0m', '1h', '2h','3h', '4h', '5h', '6h', '7h', '8h'])
plt.xticks(ticks=dates, labels=['S', 'M', 'T','W','T', 'F', 'S'])
# removing the borders from four sides
plt.gca().spines['left'].set_visible(False)
plt.gca().spines['right'].set_visible(False)
plt.gca().spines['top'].set_visible(False)
plt.gca().spines['bottom'].set_visible(False)
# adjust tick sizes
plt.tick_params(axis='x', labelsize=8)
plt.tick_params(axis='y', labelsize=8)
# Creating a horizontal line at 8h steps
plt.axhline(y=8*60, linewidth = 0.5)
#add labels
average_sleep_time = sum(asleep_new) / len(asleep_new)
hours = int((average_sleep_time//60))
mins = int(int(average_sleep_time) - hours*60)
plt.figtext(0.5,1.0, str(hours) + ' hr ' + str(mins) + " mins asleep", fontsize=15, ha='center', color ='w', fontweight = 'bold')
plt.figtext(0.5,0.96, 'Average May 15 - 21', fontsize=10, ha='center', color ='w', fontweight = 'light')
# set legend below plot
plt.legend(["", "REM", "Light","Deep","No Stages"], loc='upper center', bbox_to_anchor=(0.4, 1.1),
fancybox=True, shadow=True, ncol=5, fontsize= 8, frameon=False, handlelength=0.9)
#set legen colors
ax = plt.gca()
leg = ax.get_legend()
leg.legendHandles[0].set_color('#22204e')
leg.legendHandles[1].set_color('#81c4f9')
leg.legendHandles[2].set_color('#418cff')
leg.legendHandles[3].set_color('#184ba5')
leg.legendHandles[4].set_color('#505071')
#adding legend labels
length_rem = len(rem_new) - rem_new.count(0)
length_deep = len(deep_new) - deep_new.count(0)
length_light = len(light_new) - light_new.count(0)
length_nostages = len(no_stages_new) - no_stages_new.count(0)
hours_rem = int((sum(rem_new)/length_rem) // 60)
hours_deep = int(sum(deep_new)/length_deep //60)
hours_light = int(sum(light_new)/length_light //60)
hours_nostages = int(sum(no_stages_new)/length_nostages //60)
mins_rem = int((sum(rem_new)/length_rem) - hours_rem*60)
mins_deep = int(sum(deep_new)/length_deep - hours_deep*60)
mins_light = int(sum(light_new)/length_light - hours_light*60)
mins_nostages = int(sum(no_stages_new)/length_nostages - hours_nostages*60)
plt.figtext(0.25,0.9, str(hours_rem) + ' hr ' + str(mins_rem) + " min", fontsize=7.5, ha='center', color ='w', fontweight = 'light')
plt.figtext(0.42,0.9, str(hours_light) + ' hr ' + str(mins_light) + " min", fontsize=7.5, ha='center', color ='w', fontweight = 'light')
plt.figtext(0.59,0.9, str(hours_deep) + ' hr ' + str(mins_deep) + " min", fontsize=7.5, ha='center', color ='w', fontweight = 'light')
plt.figtext(0.76,0.9, str(hours_nostages) + ' hr ' + str(mins_nostages) + " min", fontsize=7.5, ha='center', color ='w', fontweight = 'light')
Now this looks very similar to the original graph!
5. Data analysis§
5.1 Finding Abnormalities (Outliers) in the Data§
We find outliers and remove them in order to get better analysis accuracy by removing the possibility of measurement errors, but at the same time it can affect the result’s accuracy since some outliers are true outliers: outliers that is important in the data itelf. Check this out to learn more about the effects of removing outliers.
Here we are going to find abnormalities in the data using the Elliptic Envelope algorithm, which is a machine learning algorith that creates a hypothetical ellipse around the set of data and points outside of this envelope are considered outliers. Check this to learn more about the algorithm.
We can implement this algorithm by utilizing sklearn library which has the built in Elliptic Envelope function.
[ ]:
#creating a function that detects outliers
def find_outliers(arr):
list_of_outliers = []
# Create a dataframe
d = {'arr': arr}
df = pd.DataFrame(data=d)
# here we return the a list where the indexies with -1 values are where the
# outliers are at. learn more about the implementation here:
# https://www.datatechnotes.com/2020/04/anomaly-detection-with-elliptical-envelope-in-python.html
pred = EllipticEnvelope(assume_centered=False, contamination=0.02, random_state=None,
store_precision=True, support_fraction=None).fit_predict(df['arr'].array.reshape(-1, 1))
for i in range(len(pred)):
if pred[i] == -1:
list_of_outliers.append(arr[i])
return list_of_outliers
Now lets put this to test
[ ]:
#Here we are going to take the steps as an example
#First aggregate the needed data in arrays.
dates = []
steps = []
for datapoint in api_data['steps'][0]['activities-steps']:
dates.append(datapoint['dateTime'])
steps.append(float(datapoint['value']))
# After this lets filter out the non-wear days
new_arrays = remove_non_wear([dates, steps], 1)
dates = new_arrays[0]
steps = new_arrays[1]
#inject an outlier value
steps[-1] = 2
outliers = find_outliers(steps)
print(outliers)
[2]
So this worked! Now lets try to plot the outliers!
[ ]:
#creting a list of dates that correspond to the outlier values in sleep
outlier_dates = []
for i in range(len(steps)):
if steps[i] in outliers:
outlier_dates.append(dates[i])
#creating the plot without highlighting outliers
plt.xlabel('steps')
plt.ylabel('dates')
plt.scatter(x = steps, y = dates, color = 'g')
plt.rcParams["figure.figsize"] = (5,5)
plt.show(block=True)
#recreating the plot with highlighting outliers
plt.xlabel('steps')
plt.ylabel('dates')
plt.scatter(x = steps, y = dates, color = 'g')
plt.rcParams["figure.figsize"] = (5,5)
plt.scatter(x = outliers, y = outlier_dates, color='r')
plt.show(block=True)
The outlier now appears in a different color!
5.2 Checking for Correlation Between Amount Of Sedentary Activity and The Time In Bed§
Here we are trying to see if there is a correlation between the amount of very low activity (Sedentary Activity) and the time in bed. The hypothesis is that very low intensity activity may correlate somehow with the time in bed, and we are checking for the validity of this hyposthesis.
[ ]:
#First aggregate the data in arrays
dates_activity = []
minutes = []
sleep = []
dates_sleep = []
for datapoint in api_data['minutesSedentary'][0]['activities-minutesSedentary']:
dates.append(datapoint['dateTime'])
minutes.append(float(datapoint['value']))
for datapoint in api_data['sleep'][0]['sleep']:
try:
sleep.append(float(datapoint['timeInBed'])/60)
dates_sleep.append(datapoint['dateOfSleep'])
except:
continue
# then filter the data from non-wear days
new_arrays = remove_non_wear([dates, minutes], 1)
dates_activity = new_arrays[0]
minutes = new_arrays[1]
#now adjust the arrays so that the dates match.
dates_activity_new = []
minutes_new = []
sleep_new = []
for i in dates_activity:
if i in dates_sleep:
dates_activity_new.append(i)
minutes_new.append(minutes[dates_activity.index(i)])
sleep_new.append(sleep[dates_sleep.index(i)])
#remove the outliers
outliers1 = find_outliers(minutes_new)
outliers2 = find_outliers(sleep_new)
for item in outliers1:
index = minutes_new.index(item)
minutes_new.remove(item)
sleep_new.pop(index)
for item in outliers2:
index = sleep_new.index(item)
sleep_new.remove(item)
minutes_new.pop(index)
# create a dataframex
d = {'Very Low Activity (mins)': minutes_new, 'Time in bed (Hours)': sleep_new}
df = pd.DataFrame(data=d)
#plot the data
graph = sns.lmplot(data=df, y="Very Low Activity (mins)", x="Time in bed (Hours)")
plt.show(block=True)
Lets calculate the resulting p-value. The p-value basically indicates the probability that the numbers are generated at random. A correlation is considered significant at a p-value < 0.05 which means that there is less than a 5% possibility the numbers were generated at random.
[ ]:
slope, intercept, r_value, p_value, std_err = stats.linregress(sleep_new,minutes_new)
print(p_value)
0.0332248586370972
The p-value is clearly less than 0.05 which indicates that the correlation is significant!