Inferring Cognitive Metrics from Typing Patterns
Inferring Cognitive Metrics from Typing Patterns
I wanted to see if I could extract meaningful signals about cognitive health from typing patterns. So I built a system that collects keyboard data, calculates derived metrics, and visualizes patterns over time to help identify changes in attention, impulse control, and mood stability.
System Architecture
The system has three main components:
Keyboard Data Collection
↓
[1] Data Processing Pipeline
↓
Raw Metrics (keys, backspaces, timing)
↓
[2] Cognitive Metric Calculation
↓
Attention, Impulse Control, Mood, Processing Speed
↓
[3] Analytics Dashboard
↓
Visualizations + Cohort Analysis
Stage 1: Data Collection and Processing
The system collects keyboard events including:
- Keystroke timings
- Backspace frequency
- Autocorrection usage
- Suggestion acceptance/rejection
- Session duration
All this data gets uploaded via a Django REST API and stored with timestamps. I use Celery for background processing so the API stays responsive while calculations run.
@shared_task
def process_keyboard_session(session_data):
# Extract features from raw keystroke data
features = extract_features(session_data)
# Calculate cognitive metrics
metrics = calculate_metrics(features)
# Store results
save_metrics(metrics)
Stage 2: Cognitive Metric Calculation
I calculate four main metrics from the typing patterns:
Attention: Based on typing consistency and error rate. More consistent typing with fewer errors suggests better attention.
Impulse Control: Measured by backspace frequency and autocorrection usage. Lower backspace rates and fewer corrections suggest better impulse control.
Mood Stability: Derived from typing speed variability over time. More stable typing speed suggests more stable mood.
Processing Speed: Average typing speed, adjusted for complexity of text being typed.
def calculate_metrics(features):
attention = calculate_attention(features['consistency'], features['error_rate'])
impulse_control = calculate_impulse(features['backspaces'], features['corrections'])
mood_stability = calculate_mood_stability(features['speed_variance'])
processing_speed = calculate_speed(features['avg_speed'], features['complexity'])
return {
'attention': attention,
'impulse_control': impulse_control,
'mood_stability': mood_stability,
'processing_speed': processing_speed
}
I also calculate a "clarity" metric that combines attention and processing speed:
clarity = 10 * ((processing_speed + attention) / 2)
Stage 3: Analytics Dashboard
The dashboard is built with Dash (Plotly) and shows:
- Time series of metrics over days/weeks
- Diurnal patterns (how metrics change throughout the day)
- Cohort comparisons (comparing groups of users)
- Geographic visualizations (metrics by county/FIPS code)
def get_data():
df = query_data(earliest_date, latest_date)
metrics = query_metrics(earliest_date, latest_date)
# Add temporal features
df["weekday"] = df["date"].apply(lambda x: dt.strftime(x, "%A"))
df["hourofday"] = df["date"].apply(lambda x: dt.strftime(x, "%I %p"))
return df, metrics
The dashboard lets you filter by:
- Date range
- User cohorts
- Geographic region
- Time of day
Diurnal Pattern Analysis
One of the key features is tracking how metrics change throughout the day. I group data by hour and calculate averages to show diurnal patterns:
┌─────────────────────────────────┐
│ Hour of Day vs. Metric Value │
│ │
│ Morning: Higher attention │
│ Afternoon: Lower impulse │
│ Evening: Mood varies │
└─────────────────────────────────┘
This helps identify when users are at their best cognitively, which can be useful for scheduling or treatment planning.
Cohort Analysis
I support user cohorts - groups of users that can be compared together. This is useful for:
- Clinical trials (treatment vs. control)
- Geographic comparisons
- Demographic analysis
The dashboard shows cohort-level aggregates and statistical comparisons between groups.
Data Privacy and Security
Since this is health data, privacy is critical:
- All data is de-identified at the API level
- Users are identified by UUIDs, not personal information
- Access is controlled via Django's authentication system
- The dashboard requires authentication
Challenges
Signal quality: Typing patterns vary a lot between individuals. Some people naturally type faster or make more mistakes. I normalize metrics relative to each user's baseline, but establishing baselines takes time.
Noise in the data: Real-world typing data is noisy. People type different things (emails vs. code vs. chat), which affects the metrics. I try to account for this by looking at longer-term trends rather than single sessions.
Temporal alignment: Sessions can happen at different times of day, which affects the metrics. I group by hour of day to account for diurnal patterns, but timezone handling is tricky.
Cohort definition: Defining meaningful cohorts is hard. You need enough users in each cohort to get statistical significance, but you also want cohorts that make clinical sense.
What I Learned
This project taught me a lot about building health analytics systems:
-
Derived metrics need baselines: Raw typing data isn't meaningful on its own. You need to establish baselines and normalize metrics relative to them.
-
Time-series analysis is powerful: Looking at patterns over time reveals things that single-point measurements miss. Diurnal patterns are especially informative.
-
Dashboards need to be interactive: Static charts aren't enough. Users need to be able to filter, zoom, and explore the data to find insights.
-
Privacy is non-negotiable: Health data requires careful handling. De-identification, access controls, and audit logs are essential.
-
Cohort comparisons are valuable: Being able to compare groups of users helps identify patterns and validate that metrics are meaningful.
The system provides a way to track cognitive health metrics passively, without requiring users to take tests or fill out questionnaires. It's also a good example of how to build analytics dashboards for health data that balance usefulness with privacy.