Reading

In principle, Niimpy can deal with any files of any format - you only need to convert them to a DataFrame. Still, it is very useful to have some common formats, so we present two standard formats with default readers:

  • CSV files are very standard and normal to create and understand, but in order to deal with them everything must be loaded into memory.

  • Google TakeOut provides a large selection of data in different formats. We provide readers most commonly used data types.

  • MHealth is a common format for health data.

  • sqlite3 databases, which requires sqlite3 to read, provides more power for filtering and automatic processing without reading everything into memory.

DataFrame format (in-memory)

In-memory, data is stored in a pandas DataFrame. This is basically a normal dataframe. There are some standardized columns (see the schema) and the index is a DatetimeIndex.

CSV files

CSV files should have a header that lists the column names and generally be readable by pandas.read_csv.

Reading these can be done with niimpy.read_csv:

[1]:
import os
import niimpy
import niimpy.config as config

# Read the battery data
df= niimpy.read_csv(config.MULTIUSER_AWARE_BATTERY_PATH, tz='Europe/Helsinki')

sqlite3 databases

For the purposes of niimpy, sqlite3 databases can generally be seen as supercharged CSV files.

A single database file could contain multiple datasets within it, thus when reading them a table name must be specified.

One reads the entire database into memory using sqlite.read_sqlite:

[2]:
# Read the sqlite3 data
df= niimpy.read_sqlite(config.SQLITE_SINGLEUSER_PATH, table="AwareScreen", tz='Europe/Helsinki')

You can list the tables within a database using niimpy.reading.read.read_sqlite_tables:

[3]:
niimpy.reading.read.read_sqlite_tables(config.SQLITE_SINGLEUSER_PATH)
[3]:
{'AwareScreen'}

sqlite3 files are highly recommended as a data storage format, since many common exploration options can be done within the database itself without reading the whole data into memory or writing an iterator. However, the interface is more difficult to use. Niimpy (before 2021-07) used this as its primary interface, but since then this interface has been de-emphasized. You can read more in the database section, but this is only recommended if you need efficiency when using massive amounts of data.

Google TakeOut

Google takeout contains a many different types of data and new types are added as Google creates services or changes data storage methods. Readers are currently available for location data, emails, and activity data from the fit app. For other data types, the user needs to manually convert them into a Niimpy compatible Pandas DataFrame.

[4]:
# Data downloaded from Google Takeout is compressed as a zip archive to conserve disk space. To
# demonstrate reading for the zip file, we will first compress our example data into the zip format.
import zipfile
test_zip = zipfile.ZipFile("test.zip", mode="w")

for dirpath,dirs,files in os.walk(config.GOOGLE_TAKEOUT_DIR):
    for f in files:
        filename = os.path.join(dirpath, f)
        filename_in_zip = filename.replace(config.GOOGLE_TAKEOUT_DIR, "")
        test_zip.write(filename, filename_in_zip)

test_zip.close()

Location

[5]:
# Next we read location data from the zip file.
import niimpy
import niimpy.config as config
import niimpy.preprocessing.location as nilo

data = niimpy.reading.google_takeout.location_history("test.zip")
data = nilo.filter_location(
    data,
    latitude_column = "latitude",
    longitude_column = "longitude",
    remove_disabled=False, remove_network=False, remove_zeros=True
)
data

[5]:
accuracy source device placeid formfactor altitude verticalaccuracy platformtype servertimestamp devicetimestamp batterycharging velocity heading latitude longitude inferred_latitude inferred_longitude activity_type activity_inference_confidence user
timestamp
2016-08-12 19:29:43.821000+00:00 25 WIFI -577680260 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 35.997488 -78.922194 NaN NaN NaN NaN f1066868-2e1d-11ef-abd1-b0dcef010c43
2016-08-12 19:30:49.531000+00:00 21 WIFI -577680260 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 35.997559 -78.922504 NaN NaN STILL 62.0 f1066868-2e1d-11ef-abd1-b0dcef010c43
2016-08-12 19:31:49.531000+00:00 21 WIFI -577680260 ChIJS_5Nmuz1jUYRGYf3QiiZco4 PHONE NaN NaN NaN NaN NaN NaN NaN NaN 35.997559 -78.922504 60.187135 24.824478 STILL 62.0 f1066868-2e1d-11ef-abd1-b0dcef010c43
2016-08-12 21:15:55.295000+00:00 1500 CELL -577680260 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 36.000870 -78.923343 NaN NaN ON_FOOT 54.0 f1066868-2e1d-11ef-abd1-b0dcef010c43
2016-08-12 21:16:33+00:00 8 GPS -577680260 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 35.997250 -78.923989 NaN NaN NaN NaN f1066868-2e1d-11ef-abd1-b0dcef010c43
2016-08-12 21:16:48+00:00 3 GPS -577680260 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 35.997236 -78.924124 NaN NaN NaN NaN f1066868-2e1d-11ef-abd1-b0dcef010c43
2023-11-21 11:29:21.730000+00:00 13 WIFI 1832214273 ChIJw1WKQev1jUYRCdZmYR-HCiI PHONE 28.0 2.0 ANDROID 2023-11-21T11:29:24.747Z 2023-11-21T11:29:24.350Z False 60.186818 24.821288 60.186816 24.821288 NaN NaN f1066868-2e1d-11ef-abd1-b0dcef010c43

Activity

Activity data is read similarly. The data contains many columns with missing data, so in order to use the step count data, for example, we must set the NaN values to 0.

[6]:
data = niimpy.reading.google_takeout.activity("test.zip")
data.loc[data["step_count"].isna(), "step_count"] = 0
data[["calories_(kcal)", "step_count"]]
[6]:
calories_(kcal) step_count
timestamp
2023-11-20 00:00:00+02:00 1752.250027 0.000000
2023-11-21 00:00:00+02:00 1996.456746 89.900002

Google Fit Takeout data can contain more detailed information. To access this data, we first list all data types stored in Google Fit data.

[15]:
niimpy.reading.google_takeout.fit_list_data("test.zip")
[15]:
filename derived content source source type
0 raw_com.google.step_count.delta_fi.polar.polar... raw step_count.delta fi.test.test step count
1 derived_com.google.step_count.delta_com.google... derived step_count.delta com.google.android.fit top_level
2 derived_com.google.respiratory_rate_com.google... derived respiratory_rate com.google.android.gms merge_respiratory_rate
3 raw_com.google.sleep.segment_com.urbandroid.sl... raw sleep.segment com.urbandroid.sleep saa-generic
4 raw_com.google.heart_rate.summary_fi.polar.pol... raw heart_rate.summary fi.test.testtest
5 raw_com.google.calories.expended_fi.polar.pola... raw calories.expended fi.manual.manualtest
6 derived_com.google.sleep.segment_com.google.an... derived sleep.segment com.google.android.gms merged
7 raw_com.google.nutrition_com.fourtechnologies.... raw nutrition com.manual.test
8 raw_com.google.heart_rate.bpm_nl.appyhapps.hea... raw heart_rate.bpm fi.test.test Test - heart rate
9 raw_com.google.sleep.segment_nl.appyhapps.heal... raw sleep.segment nl.test.healthsync HealthSync_sleep_by_Health_Sync_1715724879000
10 raw_com.google.heart_minutes_com.google.androi... raw heart_minutes com.google.android.apps.fitness user_input

We have two types of step count data. The original raw data is in “raw_com.google.step_count.delta_fi.polar.polar.json”. You can read this data using fit_read_data.

[17]:
niimpy.reading.google_takeout.fit_read_data("test.zip", "raw_com.google.step_count.delta_fi.polar.polar.json")
[17]:
measurement_index id value end_time modified_time datatype
timestamp
2024-04-30 17:59:58.999 0 0 8 2024-04-30 19:17:00 2024-05-01 03:15:57.130 step_count.delta
2024-04-30 17:59:58.999 1 0 8 2024-04-30 19:17:30 2024-04-30 20:02:14.506 step_count.delta

Email and Chat

The google_takeout.email_activity and google_takeout.chat function will read and process all emails in the GMail mailbox and all Google chat messages respectively. They return a dataframe containing metadata and statistics of each message. Email addresses, email IDs and names are replaced by numerical indexes.

The email files can be large and processing them could take some time. You can also include sentiment analysis of each email using the sentiment parameter. For this, we recommend using a system with a GPU.

[7]:
niimpy.reading.google_takeout.email_activity("test.zip")
/u/24/rantahj1/unix/src/niimpy/niimpy/reading/google_takeout.py:466: UserWarning: Could not parse message timestamp: 2023-12-15 12:19:43+00:00
  warnings.warn(f"Could not parse message timestamp: {received}")
/u/24/rantahj1/unix/src/niimpy/niimpy/reading/google_takeout.py:480: UserWarning: Failed to format received time: Sat, 15 DeNot a timec 2023 12:19:43 0000
  warnings.warn(f"Failed to format received time: {received}")
[7]:
received from to cc bcc message_id in_reply_to character_count word_count message_type user
timestamp
2023-12-15 12:19:43+00:00 NaT 0 [2] [] [] 2 <NA> 33 6 outgoing f10e4d26-2e1d-11ef-abd1-b0dcef010c43
2023-12-15 12:29:43+00:00 NaT 0 [6, 2] [] [] 1 <NA> 31 6 outgoing f10e4d26-2e1d-11ef-abd1-b0dcef010c43
2023-12-15 12:29:43+00:00 NaT 0 [6, 2] [] [] 1 <NA> 28 5 outgoing f10e4d26-2e1d-11ef-abd1-b0dcef010c43
2023-12-15 12:39:43+00:00 2023-12-15 12:19:43+00:00 6 [0] [4] [4] 3 2 30 5 incoming f10e4d26-2e1d-11ef-abd1-b0dcef010c43
2023-12-15 12:39:43+00:00 Sat, 15 DeNot a timec 2023 12:19:43 0000 6 [0] [4] [4] 3 2 51 7 incoming f10e4d26-2e1d-11ef-abd1-b0dcef010c43

Google chat data is read similarly.

Additionally, you can run sentiment analysis on email and chat messages using the sentiment option. To run sentiment analysis, you need to install the optional dependency by running.

pip install niimpy[sentiment]
[8]:
niimpy.reading.google_takeout.chat("test.zip", sentiment=True)
/u/24/rantahj1/unix/src/niimpy/niimpy/reading/google_takeout.py:648: UserWarning: Multiple user emails found. Using the first one.
  warnings.warn("Multiple user emails found. Using the first one.")
  0%|          | 0/4 [00:01<?, ?it/s]
[8]:
topic_id message_id chat_group creator_name creator_email creator_user_type character_count word_count user message_type sentiment sentiment_score
timestamp
2024-01-30 13:27:33+00:00 iDImYGRudHk 9guW_0AAAAE/iDImYGRudHk/iDImYGRudHk 0 0 0 Human 5 1 f1144500-2e1d-11ef-abd1-b0dcef010c43 outgoing none 0.000000
2024-01-30 13:29:10+00:00 cVEoT9zu63M 9guW_0AAAAE/cVEoT9zu63M/cVEoT9zu63M 0 1 1 Human 5 1 f1144500-2e1d-11ef-abd1-b0dcef010c43 incoming none 0.000000
2024-01-30 13:29:17+00:00 qEfkUgUvX80 9guW_0AAAAE/qEfkUgUvX80/qEfkUgUvX80 0 1 1 Human 11 3 f1144500-2e1d-11ef-abd1-b0dcef010c43 incoming positive 0.535310
2024-01-30 13:29:17+00:00 qEfkUgUvX80 9guW_0AAAAE/qEfkUgUvX80/qEfkUgUvX80 0 0 0 Human 22 5 f1144500-2e1d-11ef-abd1-b0dcef010c43 outgoing positive 0.912528

Youtube Activity

Finally, we have a reader for extracting Youtube watch history data. We do not, by default, return video identifiers, but replace them with numerical IDs. The only available information then is the recorded time, which corresponds to video start time.

Importantly, we have no information on how long the user watched a given video, as this is not stored in the TakeOut data. You can deduce whether the user has rewatched a given video, watched multiple videos in a row, or started another video quickly without finishing the previous one.

[9]:
niimpy.reading.google_takeout.youtube_watch_history("test.zip")
[9]:
video_title channel_title user
timestamp
2024-02-13 08:36:49+02:00 0 0 f1f6ae72-2e1d-11ef-abd1-b0dcef010c43
2024-02-13 08:36:05+02:00 1 1 f1f6ae72-2e1d-11ef-abd1-b0dcef010c43
2024-02-13 08:35:38+02:00 2 2 f1f6ae72-2e1d-11ef-abd1-b0dcef010c43
2024-02-13 08:35:03+02:00 0 0 f1f6ae72-2e1d-11ef-abd1-b0dcef010c43

Since Google takeout may provide the mailbox as a single uncompressed file, it is also possible to provide it’s file path directly.

[10]:
path = os.path.join(config.GOOGLE_TAKEOUT_DIR, "Takeout", "Mail", "All mail Including Spam and Trash.mbox")
niimpy.reading.google_takeout.email_activity(path, sentiment=True)
/u/24/rantahj1/unix/src/niimpy/niimpy/reading/google_takeout.py:466: UserWarning: Could not parse message timestamp: 2023-12-15 12:19:43+00:00
  warnings.warn(f"Could not parse message timestamp: {received}")
/u/24/rantahj1/unix/src/niimpy/niimpy/reading/google_takeout.py:480: UserWarning: Failed to format received time: Sat, 15 DeNot a timec 2023 12:19:43 0000
  warnings.warn(f"Failed to format received time: {received}")
Running sentiment analysis on 5 messages.
  0%|          | 0/5 [00:00<?, ?it/s]
[10]:
received from to cc bcc message_id in_reply_to character_count word_count message_type user sentiment sentiment_score
timestamp
2023-12-15 12:19:43+00:00 NaT 0 [1] [] [] 2 <NA> 33 6 outgoing f1f97e90-2e1d-11ef-abd1-b0dcef010c43 positive 0.993223
2023-12-15 12:29:43+00:00 NaT 0 [6, 1] [] [] 1 <NA> 31 6 outgoing f1f97e90-2e1d-11ef-abd1-b0dcef010c43 negative 0.980209
2023-12-15 12:29:43+00:00 NaT 0 [6, 1] [] [] 1 <NA> 28 5 outgoing f1f97e90-2e1d-11ef-abd1-b0dcef010c43 negative 0.968588
2023-12-15 12:39:43+00:00 2023-12-15 12:19:43+00:00 6 [0] [4] [4] 3 2 30 5 incoming f1f97e90-2e1d-11ef-abd1-b0dcef010c43 positive 0.997529
2023-12-15 12:39:43+00:00 Sat, 15 DeNot a timec 2023 12:19:43 0000 6 [0] [4] [4] 3 2 51 7 incoming f1f97e90-2e1d-11ef-abd1-b0dcef010c43 neutral 0.477333

General Notes on Google Takeout

Each subject Downloads their Google TakeOut data as a separate zip file. The Zipfile package, which is included in the Python standard, is convenient for reading the data files contained in the zip file. For example, one could read the location data with the following code:

[11]:
from zipfile import ZipFile
import json
import pandas as pd

zip_file = ZipFile("test.zip")
json_data  = zip_file.read("Takeout/Location History (Timeline)/Records.json")
json_data = json.loads(json_data)
data = pd.json_normalize(json_data["locations"])
data = pd.DataFrame(data)
data.head()
[11]:
latitudeE7 longitudeE7 accuracy source deviceTag timestamp activity locationMetadata placeId formFactor ... deviceDesignation altitude verticalAccuracy platformType osLevel serverTimestamp deviceTimestamp batteryCharging velocity heading
0 359974880 -789221943 25 WIFI -577680260 2016-08-12T19:29:43.821Z NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 359975588 -789225036 21 WIFI -577680260 2016-08-12T19:30:49.531Z [{'activity': [{'type': 'STILL', 'confidence':... NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 359975588 -789225036 21 WIFI -577680260 2016-08-12T19:31:49.531Z [{'activity': [{'type': 'STILL', 'confidence':... [{'wifiScan': {'accessPoints': [{'mac': '12410... ChIJS_5Nmuz1jUYRGYf3QiiZco4 PHONE ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 360008703 -789233433 1500 CELL -577680260 2016-08-12T21:15:55.295Z [{'activity': [{'type': 'ON_FOOT', 'confidence... NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
4 359972502 -789239894 8 GPS -577680260 2016-08-12T21:16:33Z NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

5 rows × 22 columns

Location data is stored in the json format. Other types of data are stored in various formats and with different files structures. The user must find how each type of data they need is stored and how it can be read in Python.

MHealth

We have implemented readers for 3 data types formatted according to the MHealth schema. These are total sleep time, heart rate and geolocation. Other data types may be added as needed.

[12]:
# Reading total sleep time data:
filename = config.MHEALTH_TOTAL_SLEEP_TIME_PATH
niimpy.reading.mhealth.total_sleep_time_from_file(filename)
[12]:
descriptive_statistic descriptive_statistic_denominator date part_of_day total_sleep_time start end
timestamp
2016-02-06 04:35:00+00:00 NaN NaN NaT NaN 0 days 07:45:00 2016-02-06 04:35:00+00:00 2016-02-06 14:35:00+00:00
2016-02-05 15:00:00+00:00 average d NaT NaN 0 days 07:15:00 2016-02-05 15:00:00+00:00 2016-06-06 15:00:00+00:00
2013-01-26 07:35:00+00:00 NaN NaN NaT NaN 0 days 03:00:00 2013-01-26 07:35:00+00:00 2013-02-05 07:35:00+00:00
2013-02-05 00:00:00+00:00 NaN NaN 2013-02-05 00:00:00+00:00 evening 0 days 03:00:00 NaT NaT
[13]:
# Reading heart rate data:
filename = config.MHEALTH_HEART_RATE_PATH
niimpy.reading.mhealth.heart_rate_from_file(filename)
[13]:
temporal_relationship_to_sleep heart_rate effective_time_frame.date_time descriptive_statistic start end
timestamp
NaT on waking 70 2023-11-20T07:25:00-08:00 NaN NaT NaT
2023-12-20 09:50:00+00:00 on waking 65 NaN NaN 2023-12-20 09:50:00+00:00 2023-12-20 10:00:00+00:00
2023-12-20 03:50:00+00:00 during sleep 35 NaN average 2023-12-20 03:50:00+00:00 2023-12-20 04:00:00+00:00
[14]:
# Reading geolocation data:
filename = config.MHEALTH_GEOLOCATION_PATH
niimpy.reading.mhealth.geolocation_from_file(filename)
[14]:
positioning_system latitude latitude.unit longitude longitude.unit effective_time_frame.time_interval.start_date_time effective_time_frame.time_interval.end_date_time elevation.value elevation.unit
0 GPS 60.1867 deg 24.8283 deg 2016-02-05T20:35:00-08:00 2016-02-06T06:35:00-08:00 NaN NaN
1 GPS 60.1867 deg 24.8283 deg 2016-02-05T20:35:00-08:00 2016-02-06T06:35:00-08:00 20.4 m

Other formats

You can add readers for any types of formats which you can convert into a Pandas dataframe (so basically anything). For examples of readers, see niimpy/reading/read.py. Apply the function niimpy.preprocessing.util.df_normalize in order to apply some standardizations to get the standard Niimpy format.