DEV Community

Selen Sherlock
Selen Sherlock

Posted on

Who Replies Faster? Measuring WhatsApp Response Times with Python

Message counts tell you who writes more. They do not tell you how quickly someone answers. To study that, you need timestamps and a rule for deciding which gaps count.

This example uses a one-to-one chat and measures the gap when the speaker changes. It is a practical approximation: even in a two-person chat, the next message may start a different topic. In a group, it becomes a next-speaker gap; you cannot reliably identify a reply's recipient from message order alone.

1. Prepare the CSV

If the conversation is in WhatsApp Web, WAExport's WhatsApp chat export can download it as CSV. Choose one conversation and the period you want to compare. Keep a copy of the original before preparing the analysis file.

Prepare a UTF-8 CSV with the three columns below. This is the script's input schema, not the exporter's guaranteed schema: rename or combine your actual date and time columns as needed.

Use YYYY-MM-DDTHH:MM:SS, keep timestamps in one fixed timezone, and preserve chronological order, including the original order of equal timestamps.

timestamp,sender,message
2026-10-19T09:00:00,Alex,Can you check the quote?
2026-10-19T09:01:00,Alex,I added the blue version.
2026-10-19T09:04:00,Sam,Checking now.
2026-10-19T09:07:00,Sam,Please send the PDF too.
2026-10-19T09:09:00,Alex,Sending it now.
2026-10-19T09:14:00,Sam,Got it.
2026-10-19T09:24:00,Alex,Anything else?
2026-10-19T12:00:00,Sam,One more question.
2026-10-19T12:06:00,Alex,Go ahead.
2026-10-19T12:10:00,Sam,Does the price include shipping?
Enter fullscreen mode Exit fullscreen mode

These are fictional messages. Remove system events such as group notices, and resolve missing sender labels before running the script. Keep a consistent label for each person.

A human-sent photo or voice note can remain a turn even when its content is unavailable. If your history spans a timezone or daylight-saving change, normalize the times first; this script assumes a fixed clock.

2. Decide what counts

  • Consecutive messages from one person form a turn. Measure from that person's last message to the other person's first message.
  • Include gaps from 0 to 60 minutes. Report longer gaps separately.
  • Attribute each included gap to the person who speaks next.
  • Show the sample count and median for each person, alongside the mean and percentage within five minutes.

In the example, Alex writes at 09:00 and 09:01, then Sam answers at 09:04. Sam gets one three-minute sample.

The 60-minute cutoff is an analysis choice, not a WhatsApp rule; it defines the active exchanges being compared.

3. Run the Python script

Save the code as response_times.py. It uses Python's standard library, including csv, so there are no packages to install.

It rejects out-of-order timestamps and files that do not contain exactly two sender labels.

"""Measure turn-transition gaps in one normalized, two-person chat.

Input timestamps must share one fixed timezone and use YYYY-MM-DDTHH:MM:SS.
Keep original order, including equal timestamps. Remove system events first.
"""
import csv
import sys
from collections import defaultdict
from datetime import datetime
from statistics import mean, median

MAX_GAP_MINUTES = 60

def main(path):
    messages = []
    with open(path, encoding="utf-8-sig", newline="") as f:
        reader = csv.DictReader(f)
        required = {"timestamp", "sender", "message"}
        if not required.issubset(reader.fieldnames or []):
            raise SystemExit("Required columns: timestamp,sender,message")
        for number, row in enumerate(reader, start=2):
            try:
                time = datetime.strptime(row["timestamp"], "%Y-%m-%dT%H:%M:%S")
                sender = row["sender"].strip()
                if not sender or row["message"] is None:
                    raise ValueError("missing sender or message")
            except (ValueError, TypeError, AttributeError) as error:
                raise SystemExit(f"Invalid CSV record {number}: {error}")
            if messages and time < messages[-1][0]:
                raise SystemExit(f"Out-of-order timestamp at CSV record {number}")
            messages.append((time, sender))

    people = sorted({sender for _, sender in messages})
    if len(people) != 2:
        raise SystemExit("Use exactly one two-person chat; check sender labels.")

    gaps = defaultdict(list)
    excluded = 0
    for previous, current in zip(messages, messages[1:]):
        if previous[1] == current[1]:
            continue
        minutes = (current[0] - previous[0]).total_seconds() / 60
        if minutes > MAX_GAP_MINUTES:
            excluded += 1
        else:
            gaps[current[1]].append(minutes)

    writer = csv.writer(sys.stdout, lineterminator="\n")
    writer.writerow(["sender", "samples", "median_min", "mean_min", "within_5_min_pct"])
    for person in people:
        values = gaps[person]
        writer.writerow([
            person, len(values),
            f"{median(values):.1f}" if values else "",
            f"{mean(values):.1f}" if values else "",
            f"{100 * sum(v <= 5 for v in values) / len(values):.1f}" if values else "",
        ])
    print(f"Excluded transitions over {MAX_GAP_MINUTES} min: {excluded}", file=sys.stderr)

if __name__ == "__main__":
    if len(sys.argv) != 2:
        raise SystemExit("Usage: python response_times.py chat.csv")
    main(sys.argv[1])
Enter fullscreen mode Exit fullscreen mode

Save your prepared data as chat.csv, put it beside the script, and run:

python response_times.py chat.csv
Enter fullscreen mode Exit fullscreen mode

4. Read the result

The fictional CSV above produces these results. The 156-minute transition at noon is excluded by the chosen cutoff.

Sender Samples Median (minutes) Mean (minutes) Within 5 min
Alex 3 6.0 6.0 33.3%
Sam 3 4.0 4.0 100.0%

Sam's included intervals are 3, 5, and 4 minutes; Alex's are 2, 10, and 6. That explains the medians. Three samples each demonstrate the calculation; they do not establish a reliable ranking.

For a real comparison, use a longer period, show the included sample counts, and rerun with 15-, 60-, and 180-minute cutoffs by changing MAX_GAP_MINUTES. If the ordering changes, report that sensitivity.

If the source records only minutes, a zero gap means the same recorded minute, not an instant response.

Unanswered messages, the first turn in the file, and gaps above the cutoff do not contribute to the median. The result describes included turn changes, not total waiting time or someone's willingness to help.

For a customer-service measure, identify actual incoming requests and their first qualifying responses instead.

Top comments (0)