Skip to content
← All articles
Article

Economics of LEGO Sets with Data Science

Listen to the audio story

Can a LEGO set's price be forecast? · 2:10 · AI-written script, synthetic voices

Transcript

Ava: There's a LEGO set from 1965 whose resale price people actually plot on a chart. So naturally Antoine asked: can a simple model predict where it goes next?

Andrew: A forecast for a pile of plastic bricks. I like where this is going.

Ava: He's a collector who writes Python. So he combined two sources. Rebrickable, which publishes the catalog: sets, themes, parts, colors. And BrickEconomy, which has resale values, but no download button.

Andrew: So he scraped it.

Ava: With Playwright, yes. And here's a fun detail: the price history isn't in the page's visible table. It sits in an inline script that feeds a chart, so his code reads the script tags and parses the rows out of them.

Andrew: Sneaky. What did the catalog data show?

Ava: Two trends. The number of sets per year climbs steeply after the late nineteen-nineties and passes a thousand in recent years. And the average part count rises too, from under a hundred for most of the seventies and eighties to around three hundred at the peak. Star Wars leads the themes by a wide margin.

Andrew: Now the test case. The set from 1965?

Ava: Set 001-1, the Samsonite Gears set. Long history, enough resale activity to plot. He cleaned the data, stripped currency symbols, dropped zero values, and fit a plain linear regression of value against date, projected out to 2030.

Andrew: Does it work?

Ava: Antoine is honest about it. It's not a trading signal. It's a way to ask simple questions, like whether the trend dominates or how far off the recent spikes above six hundred are. He also warns that the prices were shown in Canadian dollars even though a column was named USD.

Andrew: What else should you distrust?

Ava: A broken scraper makes missing months look like price crashes. Discontinued sets skew the counts. And a linear model on hype sets falls apart.

Andrew: So the takeaway?

Ava: Great practice for pandas and async scraping, and a reminder that resale prices are sentiment plus plastic. The code is in the LegosTracker repo, and the full write-up has all the charts.

Economics of LEGO Sets with Data Science
Table of contents

I collect LEGO and write Python, so I merged Rebrickable exports with a BrickEconomy scraper to see how sets and themes evolved, and whether a simple model could follow the resale price of a classic set like 001-1. The notebooks and scraper live in AlgoETS/LegosTracker[1].

1. Data sources

DatasetWhat it gives you
Rebrickable CSVssets, themes, parts, colors, inventories: good for counts and joins
BrickEconomy (scraped)value history and sealed-price ranges that Rebrickable does not ship

Rebrickable publishes sets.csv, themes.csv, parts.csv, colors.csv and the inventory tables. The schema below is how they join.

Rebrickable tables and their keys

2. Sets over the decades

Grouping sets by year gives two trends:

sets.groupby('year')['name'].nunique().plot(kind='bar')
plt.title("The Numbers of Sets by Year")
plt.show()

parts_by_year = sets[['year', 'num_parts']].groupby('year', as_index=False).mean()
parts_by_year.plot(x='year', y='num_parts', color="purple")
plt.title("Average Number of Parts by Year")
plt.show()
  1. The number of sets per year climbs steeply after the late 1990s and passes 1,000 in recent years.
  2. The average part count per set rises too, from under 100 for most of the 1970s and 1980s to around 300 at the peak.

Number of sets released per year

Average parts per set by year

3. Themes that dominate the catalog

Count sets per theme_id, join themes, keep top-level themes (no parent_id) and plot the ten largest:

set_themes = sets["theme_id"].value_counts()
set_themes = pd.DataFrame({"id": set_themes.index, "count": set_themes.values})
set_themes = pd.merge(set_themes, themes, on="id")
set_themes_no_parent = set_themes[pd.isnull(set_themes["parent_id"])]
set_themes_top_10 = set_themes_no_parent.sort_values(by=["count"], ascending=False)[:10]
top_10 = set_themes_top_10["count"]
top_10.index = set_themes_top_10["name"]
top_10.plot.bar(color="gold", rot=30)

Top 10 themes by number of sets

Star Wars leads by a wide margin, followed by Friends, Gear, Ninjago, Bionicle and Technic. Gear and Service Packs are a reminder that the catalog counts more than buildable sets.

4. Why scrape BrickEconomy?

Rebrickable answers what exists. It does not say what people pay on the secondary market. BrickEconomy shows value estimates, growth forecasts and sales trends per set, so I wrote a scraper with Playwright, asyncio, pydantic models for validation, and aiohttp.

What BrickEconomy publishes for a set: value, growth forecast, sales trend

pip install playwright pydantic aiohttp pandas scikit-learn matplotlib
playwright install

The price history is not in the HTML table. It sits in an inline script that feeds a Google Charts DataTable, so parse_history reads the <script> tags and parses the rows out of it.

The inline chart script that holds the price history

The full LegoAPI class (navigation, parse_history, parse_set, CSV writing) is in the repository and the Medium original[2].

5. Price history for set 001-1

Set 001-1, the 1965 Samsonite Gears set, is a good test case: long history and enough resale activity to plot.

BrickEconomy page for 001-1 Samsonite Gears

The scraper writes two files per set. 001-1_history.csv holds dated values with annotations such as the release, and 001-1_new.csv holds monthly value ranges for sealed copies.

001-1_history.csv: dated values and annotations

001-1_new.csv: monthly value ranges

Cleaning removes duplicates, strips currency symbols, parses dates and drops zero values:

df_0011.drop_duplicates(inplace=True)
df_0011['Currency Value'] = df_0011['Currency Value'].fillna(0)
df_0011['Currency Value'] = df_0011['Currency Value'].str.replace('[^\d.]', '', regex=True)
df_0011['Currency Value'] = pd.to_numeric(df_0011['Currency Value'], errors='coerce')
df_0011.drop(['Value', 'Status'], axis=1, inplace=True)
df_0011['Date'] = pd.to_datetime(df_0011['Date'], errors='coerce')
df_0011.rename(columns={'Currency Value': 'USD Value', 'Description': 'Set Description'}, inplace=True)
df_0011['USD Value'] = df_0011['USD Value'].fillna(0)
df_0011 = df_0011[df_0011['USD Value'] != 0]

One caveat: BrickEconomy displayed Canadian dollars for my session (C$ in the raw values), and the notebook renames the column to USD Value anyway. Read the axis below as Canadian dollars.

Notebook output: lowest and highest priced sets, average price

6. Linear regression as a humble forecast

The model is a plain linear regression of value against the date, with a projection to 2030:

X = df_0011['Date'].map(pd.Timestamp.toordinal).values.reshape(-1, 1)
y = df_0011['USD Value'].values

model = LinearRegression().fit(X, y)

future_dates = pd.date_range('2008-12-31', '2030-12-31', freq='Y')
future_dates_ordinal = future_dates.map(pd.Timestamp.toordinal).values.reshape(-1, 1)
future_predictions = model.predict(future_dates_ordinal)

Linear regression of set 001-1 with future predictions

It is not a trading signal. It is a way to ask simple questions: does the trend dominate, do the residuals cluster by era, and how far off are the recent spikes above 600? Plotting the gain between observations makes those spikes obvious.

Gain over time and value over time for 001-1

7. When not to trust this analysis

PitfallEffect
Scraper breakageMissing months look like price crashes
Survivorship in the sets tableDiscontinued sets skew counts
Linear model on hype setsResiduals explode
Treating resale as MSRPA different economic story

8. Takeaway

LEGO data is messy fun: good for pandas practice, async scraping discipline, and remembering that secondary prices are sentiment plus plastic, not fundamentals.

10. References


Originally published on Medium[2].

References

  1. AlgoETS. (). LegosTracker. [Software repository] github.com.
  2. Medium original. (). [Article] medium.com.
  3. Rebrickable downloads. (). [Web page] rebrickable.com.
  4. BrickEconomy. (). [Web page] www.brickeconomy.com.
  5. hevalhazalkurt. (). exploring_the_data_of_LEGO_history. [Software repository] github.com.

References use this article’s publication date.