WEIR

Twitter Data Analysis in Python with pandas (2026)

Load real X posts into pandas, drop reposts, find posting hours in UTC and rank posts by engagement: Twitter data analysis in Python, with real output.


Published · Updated · 7 min read

Cover image: Seven ESA posts in pandas

Twitter data analysis in Python starts with one move: load the posts into a pandas DataFrame, then count, group and rank them.

This tutorial is for you if you have X (Twitter) posts and want answers, not a word cloud. You get six short steps, run on seven real posts from the European Space Agency, with every output shown. We run Weir, the API that fetched them.

Paste this to save an account's latest posts as esa.json:

import json, os, requests

r = requests.post("https://api.weirapp.io/v1/x/search", timeout=60,
                  headers={"Authorization": f"Bearer {os.environ['WEIR_API_KEY']}"},
                  json={"fromUsers": ["ESA"], "limit": 20})
r.raise_for_status()  # a 402 means you're out of credits
with open("esa.json", "w", encoding="utf-8") as file:
    json.dump(r.json(), file, ensure_ascii=False)
  1. Save one search to JSONOne API call, then work offline.
  2. Load it into pandasIds stay strings.
  3. Drop repostsTheir counts belong to the original.
  4. Find when posts go outWeekday and hour, in UTC.
  5. Score engagementPer view, then the top posts.
  6. Compare formatsPhoto, video or text.

Step 1: Save one search to a JSON file

Pull your data once and keep the raw file, so every rerun reads the file, not the API.

You need Python 3, a free Weir key, and two packages:

pip install pandas requests

Then run the snippet at the top of this page. It saves Weir's whole response, with X's own field names.

We ran the same search with limit: 7 through Weir's MCP server on 22 October 2026. Here is what came back:

7posts from @esa, 20 to 22 October 2026
2of them reposts
8credits: 1 for the call, 1 per post

Seven posts is a teaching sample, small enough to check by eye. For a real study, page through more with the cursor, as our guides to getting tweets with Python and X API pagination show.

Step 2: Analyze tweets with pandas, starting from a DataFrame

pd.DataFrame turns the list of posts into a table, one row per post. We ran every block below with pandas 3.1.0.

import json
import pandas as pd

with open("esa.json", encoding="utf-8") as file:
    posts = json.load(file)["data"]

df = pd.DataFrame(posts)  # ids stay strings
print(df[["id", "createdAt", "likeCount", "viewCount"]].head(3).to_string(index=False))
                 id                createdAt  likeCount  viewCount
2113289769668976839 2026-10-22T15:21:58.000Z         40       5834
2113241032913547768 2026-10-22T12:08:19.000Z         69      11858
2113239335428329518 2026-10-22T12:01:34.000Z         32       8178

The ids arrive as strings, and this keeps them that way.

When you read a CSV back, pass dtype={"id": str} to keep every digit.

Step 3: Drop reposts before you count anything

X search returns reposts by default, and so does Weir's. A repost has 0 likes of its own; its real counts sit in retweetedPost.

df["is_repost"] = df["retweetedPost"].notna()
reposts = df[df["is_repost"]]
print(reposts["likeCount"].tolist(), reposts["retweetedPost"].str.get("likeCount").tolist())

own = df[~df["is_repost"]].copy()
print(len(df), "posts,", len(own), "original")
[0, 0] [211, 102]
7 posts, 5 original

Both reposts came from @esaspaceflight, ESA's human spaceflight account. Their likes belong to those posts, not to @esa.

To skip reposts at the source, add -filter:nativeretweets to your query. Quote posts stay in, since a quote is the account's own post.

Step 4: Find when the account posts, by weekday and hour

Convert createdAt to a real time first. utc=True keeps every post on one clock:

own["created"] = pd.to_datetime(own["createdAt"], utc=True)
own["weekday"] = own["created"].dt.day_name()
own["hour"] = own["created"].dt.hour

print(own["weekday"].value_counts().to_string())
print(own["hour"].value_counts().sort_index().to_string())
weekday
Thursday    3
Tuesday     2
hour
12    4
15    1

Four of the five posts went out in the 12:00 UTC hour. ESA's headquarters are in Paris, so convert to local time:

own["paris_hour"] = own["created"].dt.tz_convert("Europe/Paris").dt.hour

That turned hour 12 into 14. It matches the "LIVE NOW" post at 12:01 UTC, for a briefing ESA had set for 14:00 CEST.

Five posts can't show a weekly habit, though. Pull a month before you plan around it.

Step 5: Score engagement per post and find the top posts

Likes alone favor posts that more people saw. Divide by views instead:

engagements = likes + reposts + replies + quotes + bookmarks
engagement rate = engagements / views × 100

In pandas, that's one sum across five columns:

COUNTS = ["likeCount", "retweetCount", "replyCount", "quoteCount", "bookmarkCount"]
own["engagements"] = own[COUNTS].fillna(0).sum(axis=1)
own["rate"] = (own["engagements"] / own["viewCount"] * 100).round(2)

own["start"] = own["text"].str[:24]
top = own.sort_values("rate", ascending=False)
print(top[["start", "engagements", "viewCount", "rate"]].to_string(index=False))
                   start  engagements  viewCount  rate
Galileo Second Generatio          320      13261  2.41
At its 350th meeting, ou           51       5834  0.87
The 350th ESA Council ha           90      11858  0.76
Join us live on 22 Octob          160      21531  0.74
🔴 LIVE NOW: media briefi           40       8178  0.49

Here are the same rates as a chart:

@esa engagement rate by views, pulled 22 October 2026
Galileo satellite update2.41%
Council outcomes0.87%
New directors named0.76%
Briefing invite0.74%
LIVE NOW link0.49%

The Galileo post, about Europe's satellite navigation system, won by a wide margin.

Read the second row with care: that post was under two hours old when we pulled it. For a fair ranking, leave out the last day.

To judge whether a rate is good, see our Twitter engagement rate guide.

Step 6: Compare photo, video and text posts

Each post's media list holds X's own type names: photo, video or animated_gif. An empty list means text only.

own["format"] = own["media"].apply(lambda m: m[0]["type"] if m else "text")
print(own.groupby("format")["rate"].agg(["count", "median"]).to_string())
        count  median
format
photo       4   0.815
text        1   0.490

Photos had a higher median, but one text post is an anecdote. Run this on a few hundred posts before you change your plan.

For more than one account, our Twitter competitor analysis compares brands with the same formula.

What does a Twitter data analysis project in Python cost?

On Weir, a search costs 1 credit per call plus 1 per post. X serves about 20 posts per page, so 1,000 posts take about 50 calls.

Pulling 1,000 posts, prices as of October 2026
X API$5
Weir, $5 packabout $0.21
TwitterAPI.io$0.15

TwitterAPI.io is cheapest here. Weir's 5,000 free credits a month cover about 4,760 posts.

If your analysis doesn't work

  • Ids end in different digits. A float column rounded them; read CSVs with dtype={"id": str}.
  • UnicodeEncodeError on Windows. Ours hit it on emoji when output went to a file. Run python -X utf8 analyze.py.
  • A rate shows NaN. X reported no views for that post, and pandas skips it in medians.
  • KeyError: 'data'. The call failed; print r.json() to see why. A 402 means no credits left.

When not to use Weir

  • Price per post matters most. TwitterAPI.io lists $0.15 per 1,000 tweets, and it can post too.
  • Your company needs a contract with X. X's own API charges $0.005 per post read.
  • You want charts without code. Weir returns data, and the analysis is yours to build.
  • Access must never change. Weir isn't licensed by X, so it can.

Weir reads public posts only. X's terms still apply to how you use the data, and this isn't legal advice.

FAQ

How do I analyze tweets with pandas?

Load the saved JSON's data list with pd.DataFrame, keeping ids as strings. Drop reposts, convert createdAt with utc=True, and group by weekday or hour. Then rank posts by engagements per view.

What is a good Twitter data analysis project in Python?

Start small: one account, one month, three questions. When does it post, which posts earn the most per view, and do photos beat text? Then compare a few rival brands, or add sentiment scores with our VADER tutorial.

Can I do tweet analysis in Python for free?

Yes, at small volumes. pandas is free, and Weir's 5,000 free monthly credits cover about 4,760 posts, with no card. X's own API charges $0.005 per post read.

Why do tweet ids change when I load them in pandas?

When pandas reads JSON or CSV, it turns ids into numbers, and a column with a gap becomes a float. A float keeps about 16 digits, so a 19-digit id gets rounded. Build the DataFrame from the parsed JSON, or read CSVs with dtype={"id": str}.

Sources

All checked on 22 October 2026: