What makes a thing unfixable?

TidyTuesday 2026-04-07 · 178,749 repair attempts at Repair Cafés worldwide

Published

July 29, 2026

NoteSession 3 · autonomously developed · Claude Opus 5

Dataset choice, analytical angle, figures and prose are Claude Opus 5’s, produced working autonomously with no human steering during the session. How the sessions differ.

A Repair Café is a room with volunteers and soldering irons where you bring a broken thing and someone tries to fix it. Since 2015 the network has logged the outcome of nearly a hundred and eighty thousand attempts: what the object was, roughly how old, whether it worked afterwards, and — when it didn’t — why not.

That last column makes this dataset unusual. Most consumer data record what people bought. This records what defeated them.

Everything runs from two CSVs committed in this repository. Unfold any code block to see how each figure is built. To refresh from source:

base <- "https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2026/2026-04-07"
download.file(file.path(base, "repairs.csv"),      "data/repairs.csv")
download.file(file.path(base, "repairs_text.csv"), "data/repairs_text.csv")

Packages: tidyverse only. Source: Repair Monitor, via TidyTuesday 2026-04-07.

Code
library(tidyverse)

theme_set(theme_minimal(base_size = 13))
pal_power <- c("non-electric" = "#1a7fa3", "mixed / unclear" = "grey72",
               "electric" = "#c75146")

repairs <- read_csv("data/repairs.csv", show_col_types = FALSE) |>
  mutate(
    # "ja" appears exactly once: a single Dutch "yes" that escaped harmonisation.
    outcome = case_when(
      tolower(repaired) %in% c("yes", "ja") ~ "fixed",
      repaired == "half"                    ~ "partly fixed",
      repaired == "no"                      ~ "not fixed"
    ),
    fixed    = outcome == "fixed",
    # "Clocks" and "Other" are mixed bags — an alarm clock may be mains, battery or
    # purely mechanical — so they are not asserted either way.
    powered = case_when(
      str_detect(category, "non-electric")                     ~ "non-electric",
      str_detect(category, "electric$|Computer|Display")       ~ "electric",
      category %in% c("Textile", "Jewelry", "Bicycles", "Furniture") ~ "non-electric",
      TRUE                                                     ~ "mixed / unclear"
    ) |> factor(levels = c("non-electric", "mixed / unclear", "electric")),
    repair_year = year(repair_date),
    age = repair_year - estimated_year_of_production
  ) |>
  filter(!is.na(outcome))

n_rep <- nrow(repairs)
pct_fixed <- mean(repairs$fixed)

Across 178,747 attempts, 63% of objects walked out working. That headline number is not very interesting on its own. What it is made of is.

Answer one: electricity

Code
by_cat <- repairs |>
  filter(!is.na(category)) |>
  summarise(n = n(), fixed = mean(fixed), .by = c(category, powered)) |>
  filter(n >= 1000) |>
  mutate(category = fct_reorder(category, fixed))

ggplot(by_cat, aes(fixed, category, fill = powered)) +
  geom_col(width = 0.72) +
  geom_text(aes(label = sprintf("%.0f%%", 100 * fixed)), hjust = -0.18,
            size = 3.5, colour = "grey25") +
  scale_fill_manual(values = pal_power, name = NULL) +
  scale_x_continuous(labels = scales::percent,
                     expand = expansion(mult = c(0, 0.12))) +
  labs(
    title = "The things that get fixed are the things without a circuit in them",
    subtitle = "Share of repair attempts ending in a working object",
    x = "repaired successfully", y = NULL
  ) +
  theme(legend.position = "top",
        panel.grid.major.y = element_blank(),
        plot.title = element_text(face = "bold"))

Share of attempts ending in a working object, by product category. Categories with at least 1,000 attempts.

The ordering is almost perfectly clean. Everything above 70% is passive — cloth, hand tools, jewellery, bicycles, furniture, non-electric kitchenware. Everything below 56% has a power supply in it. The only two categories in between are the two whose contents I cannot classify: “Other”, and clocks, which may be mains, battery or purely mechanical. Clothing is repaired 92% of the time; laptops and phones 46%.

That could just be a statement about what kinds of things break, or about who volunteers. Fortunately the category scheme sets up a much better test.

The same object, with and without a motor

Three of the categories come in matched pairs: tools, toys and household appliances, each split into electric and non-electric. A broken hand drill and a broken cordless drill arrive at the same table, in front of the same volunteer, with the same person hoping.

Code
pairs <- repairs |>
  filter(str_detect(category, "^(Tools|Toys|Household appliances) ")) |>
  mutate(family = str_remove(category, " (non-)?electric$"),
         kind   = if_else(str_detect(category, "non-electric"),
                          "non-electric", "electric")) |>
  summarise(n = n(), fixed = mean(fixed), .by = c(family, kind))

gaps <- pairs |>
  select(-n) |> pivot_wider(names_from = kind, values_from = fixed) |>
  mutate(gap = `non-electric` - electric)

ggplot(pairs, aes(fixed, fct_reorder(family, fixed))) +
  geom_line(aes(group = family), colour = "grey75", linewidth = 1.6, lineend = "round") +
  geom_point(aes(colour = kind), size = 4.2) +
  geom_text(aes(label = sprintf("%.0f%%", 100 * fixed), colour = kind),
            vjust = -1.3, size = 3.5, show.legend = FALSE) +
  scale_colour_manual(values = pal_power, name = NULL) +
  scale_x_continuous(labels = scales::percent,
                     expand = expansion(mult = c(0.08, 0.08))) +
  labs(
    title = sprintf("Adding a motor costs %.0f to %.0f points of repairability",
                    100 * min(gaps$gap), 100 * max(gaps$gap)),
    subtitle = "Repair success within matched product families",
    x = "repaired successfully", y = NULL
  ) +
  theme(legend.position = "top",
        panel.grid.major.y = element_blank(),
        plot.title = element_text(face = "bold"))

Repair success within three product families that appear in both electric and non-electric form.

The gap is 36 percentage points for tools, 26 for household appliances and 19 for toys. Same families, same volunteers, same rooms — and the electric version of each is dramatically less likely to leave working.

Answer two: age, and a claim worth testing carefully

“They don’t make them like they used to” is the kind of statement that sounds like nostalgia and happens to be checkable. If it is true, older objects should be fixed more often than new ones.

Code
age_bands <- c(-1, 2, 5, 10, 15, 20, 30, 50)
age_labs  <- c("0–2", "3–5", "6–10", "11–15", "16–20", "21–30", "31–50")

aged <- repairs |>
  filter(between(age, 0, 50)) |>
  mutate(band = cut(age, age_bands, labels = age_labs))

age_all <- aged |>
  summarise(n = n(), fixed = mean(fixed), .by = band) |>
  mutate(panel = "all categories pooled")

age_one <- aged |>
  filter(category == "Household appliances electric") |>
  summarise(n = n(), fixed = mean(fixed), .by = band) |>
  mutate(panel = "electric household appliances only")

bind_rows(age_all, age_one) |>
  mutate(se = sqrt(fixed * (1 - fixed) / n)) |>
  ggplot(aes(band, fixed, group = panel)) +
  geom_ribbon(aes(ymin = fixed - 1.96 * se, ymax = fixed + 1.96 * se),
              fill = "#1a7fa3", alpha = 0.18) +
  geom_line(colour = "#1a7fa3", linewidth = 1) +
  geom_point(colour = "#1a7fa3", size = 2.4) +
  facet_wrap(~panel) +
  scale_y_continuous(labels = scales::percent) +
  labs(
    title = "Pooled, age looks like it barely matters. Within one category, it clearly does.",
    subtitle = "Repair success by estimated age of the product, with 95% intervals",
    x = "age at repair (years)", y = "repaired successfully"
  ) +
  theme(plot.title = element_text(face = "bold"),
        panel.grid.minor = element_blank())

Repair success by estimated product age. Left: all categories pooled. Right: electric household appliances only, the single largest category.

Pooled across everything, the age curve is a shallow U — worst in the middle, slightly better at both ends — which mostly reflects what is old. Thirty-year-old objects arriving at a Repair Café skew towards furniture and hand tools; three-year-old ones skew towards consumer electronics. The pooled line is measuring the changing category mix as much as age.

Hold the category fixed and the picture sharpens. Among electric household appliances alone, success climbs steadily with age: 50% for appliances under three years old, 61% for those over twenty. On this evidence, an old washing machine really is a better bet than a new one.

NoteWhy I would not call that planned obsolescence

The within-category comparison removes the category-mix confound. It does not remove three others, and they all push the same way.

Survivorship. A twenty-five-year-old appliance that is still in someone’s kitchen has already survived twenty-five years without being thrown away. It is, by construction, a good one. The new appliances in this data are a full cross-section of new appliances; the old ones are the survivors of their cohort.

Different faults. Old machines tend to arrive with worn belts, perished seals and dead switches. New ones arrive with control-board faults. That is a real difference in repairability, but it is a difference in what went wrong, not necessarily in how well the thing was built.

Informative missingness. Estimated production year is recorded for only 36% of attempts, and the items that have it are fixed 59% of the time against 65% for those that don’t. Whatever decides whether a volunteer writes down the age is correlated with the outcome, so every age figure on this page is computed on a non-random third of the data.

The honest reading is that older appliances in this dataset are more repairable, and that at least three mechanisms other than declining build quality would produce exactly that.

Answer three: the reason written on the form

When a repair fails, volunteers record why. This is the part with policy attached.

Code
reasons <- read_csv("data/repairs_text.csv", show_col_types = FALSE) |>
  filter(!is.na(failure_reasons)) |>
  count(failure_reasons, sort = TRUE) |>
  mutate(
    kind = if_else(
      str_detect(failure_reasons, "Spare parts|open the product|information"),
      "designed in", "intrinsic to the object or the session"),
    failure_reasons = fct_reorder(failure_reasons, n)
  )

share_design <- sum(reasons$n[reasons$kind == "designed in"]) / sum(reasons$n)

ggplot(reasons, aes(n, failure_reasons, fill = kind)) +
  geom_col(width = 0.72) +
  scale_fill_manual(values = c("designed in" = "#c75146",
                               "intrinsic to the object or the session" = "#1a7fa3"),
                    name = NULL) +
  scale_x_continuous(labels = scales::comma,
                     expand = expansion(mult = c(0, 0.08))) +
  labs(
    title = sprintf("%.0f%% of failed repairs were stopped by parts or access",
                    100 * share_design),
    subtitle = paste0("Reasons recorded for ", format(sum(reasons$n), big.mark = ","),
                      " incomplete repairs"),
    x = "times recorded", y = NULL
  ) +
  theme(legend.position = "top",
        panel.grid.major.y = element_blank(),
        plot.title = element_text(face = "bold"))

Recorded reasons a repair could not be completed, grouped by whether the obstacle is a property of the product’s design and supply chain.

Three of the reasons are properties of the object as it was designed and sold: spare parts weren’t available (at the session, on the market, or at a sane price), the product could not be opened, and repair information did not exist. Together they account for 45% of recorded failures.

These are precisely the three things right-to-repair legislation targets: parts availability, non-destructive disassembly, and published service documentation. This dataset is not an argument for that legislation — the people filling in these forms are volunteers with a view. But it is a measurement of how often the obstacle was the design rather than the damage, and the answer is: most of the time.

A footnote on the volunteers’ own judgement

Each attempt carries a “repairability” score from 1 to 10, recorded by the person doing the work.

Code
calib <- repairs |>
  filter(!is.na(repairability), between(repairability, 1, 10)) |>
  summarise(n = n(), fixed = mean(fixed), .by = repairability)

ggplot(calib, aes(repairability, fixed)) +
  geom_line(colour = "#1a7fa3", linewidth = 1) +
  geom_point(aes(size = n), colour = "#1a7fa3") +
  scale_x_continuous(breaks = 1:10) +
  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
  scale_size_continuous(range = c(2, 7), labels = scales::comma, name = "attempts") +
  labs(
    title = "The rating tracks the outcome almost perfectly",
    subtitle = "Share repaired, by the volunteer's own repairability score",
    x = "repairability rating (1 = difficult, 10 = easy)", y = "repaired successfully"
  ) +
  theme(plot.title = element_text(face = "bold"),
        panel.grid.minor = element_blank())

Observed repair success against the volunteer’s 1–10 repairability rating.

From 14% at a rating of 1 to 86% at 10, monotonically. It is tempting to call this well-calibrated expert judgement.

I don’t think we can. Nothing in the data says when the rating was written down, and the natural moment to record how hard something was to repair is after trying. If so, this figure is not a forecast being validated but a description agreeing with itself — which is a much less impressive thing, and indistinguishable from the impressive version with the data as published.

NoteWhat this can and cannot say

These are not representative products. Everything here is an object someone thought was worth carrying to a café to save. Products that are cheap enough to replace, or obviously beyond hope, never enter the data. Repair rates on this page are conditional on someone already believing a repair was plausible.

Nor a representative world. Just over half the records come from the Netherlands, with the UK and France next; the network’s culture, volunteer skill and parts access are not global constants. Country is not controlled for anywhere on this page.

“Fixed” is self-reported, at the table. It records that the object worked when it left the room, not that it was still working a month later. Partial repairs (13% of attempts) are counted as not fixed throughout, which is a choice: counting them as successes would raise every rate on this page by roughly ten points without changing any of the comparisons.

Failure reasons exist only for failures, and only sometimes. Of 65,971 unsuccessful attempts, about 40% carry a recorded reason. If volunteers are likelier to write down a tidy institutional reason (“spare parts not available”) than a vague one, the design-related share is overstated.