Bike-Share Trips That Last 25 Hours: Lost Bikes and Capped Durations

On this page

Problem statement

Sort a year of bike-share trips by duration and the top of the list is a wall: thousands of trips of almost exactly 1,500 minutes. Nobody rode for 25 hours. These are rentals the system closed because the bike was never docked again β€” lost, stolen, abandoned, or docked so badly that the dock never registered it. They have no end station and usually no end position, and every one of them adds a day to any average it enters.

This page finds the rental caps in Capital Bikeshare's 2025 trips for Washington, D.C., looks at who takes the bikes that do not come back, from where and when, and shows what the capped rentals do to station statistics if they are left in.

Quick answer

A capped rental is one whose duration sits on the system's limit β€” 1,500 minutes for a classic bike, 480 for an e-bike. Flag those, and look at whether they have an end position:

cap = {"classic_bike": 1500, "electric_bike": 480}
at_cap = (t.minutes - t.rideable_type.map(cap)).abs() <= 1
how = at_cap.map({True: "closed at the cap", False: "within the cap"}).rename("rental")
where = t.end_lat.isna().map({True: "no end position", False: "end position"}).rename("end")
print(pd.crosstab(t.rideable_type, [how, where]).to_string())
rental        closed at the cap                 within the cap                
end                end position no end position   end position no end position
rideable_type                                                                 
classic_bike                129            4410        2408098              85
electric_bike                56               0        4249798               0

4,539 classic-bike rentals were closed at 25 hours, and 4,410 of those have no end position: the bike was never seen again by the system that day. 129 were closed at the cap but do have an end, the bike having turned up at a dock later. 85 classic-bike trips within the cap also lack an end position, which is a different kind of gap. E-bikes are different again: 56 were closed at eight hours, every one of them with an end position, because an e-bike reports its own location.

Histogram on a log scale of 2025 trips longer than two hours. Both bike types fall off steeply after two hours; e-bike trips end abruptly at 480 minutes with a small spike there, while classic trips continue at a low, flat level to a tall spike of several thousand trips at 1,500 minutes. Histogram on a log scale of 2025 trips longer than two hours. Both bike types fall off steeply after two hours; e-bike trips end abruptly at 480 minutes with a small spike there, while classic trips continue at a low, flat level to a tall spike of several thousand trips at 1,500 minutes.
The long tail of trip durations. The two spikes are the system closing rentals, at different limits for each bike type.

Caps, riders, stations and seasons

1. Find the caps in the duration tail

The caps are not documented on the system data page, but they are easy to find: count the long trips in ten-minute bins and look for the spike.

for kind, d in t.groupby("rideable_type"):
    tail = d.minutes[d.minutes > 300].round(-1).value_counts().sort_index()
    print(kind, "trips over 5 h, by 10-min bin, largest bins:", tail.nlargest(3).to_dict(), " max", round(d.minutes.max(), 1))
classic_bike trips over 5 h, by 10-min bin, largest bins: {1500.0: 4547, 310.0: 131, 350.0: 112}  max 1560.0
electric_bike trips over 5 h, by 10-min bin, largest bins: {480.0: 59, 310.0: 38, 320.0: 34}  max 480.0

For classic bikes the 1,500-minute bin holds 4,547 trips, against 131 in the next largest bin over five hours. For e-bikes the spike is at 480 minutes and nothing is longer. A few classic trips run to 1,560 minutes; the daylight-saving page shows that this is the cap plus the hour gained when a rental spans the clock change.

2. Who takes the bikes that do not come back

lost = t[at_cap & (t.rideable_type == "classic_bike")]
print(f"{len(lost):,} classic-bike rentals closed at 25 hours")
print("member share:", f"{(lost.member_casual == 'member').mean():.1%}", "against", f"{(t.member_casual == 'member').mean():.1%} of all trips")
print("start hour, share of these vs all classic trips:")
cl = t[t.rideable_type == "classic_bike"]
print(pd.DataFrame({"closed at cap": lost.started_at.dt.hour.value_counts(normalize=True),
                    "all classic": cl.started_at.dt.hour.value_counts(normalize=True)}).loc[[0, 3, 8, 12, 17, 21]].round(3).to_string())
4,539 classic-bike rentals closed at 25 hours
member share: 26.9% against 71.0% of all trips
start hour, share of these vs all classic trips:
            closed at cap  all classic
started_at                            
0                   0.024        0.010
3                   0.010        0.002
8                   0.044        0.070
12                  0.063        0.058
17                  0.086        0.105
21                  0.036        0.035

Casual riders take most of the bikes that do not come back: members account for only 26.9% of capped rentals, against 71.0% of all trips. The hours lean late β€” 2.4% of these rentals started in the midnight hour against 1.0% of classic trips, and 1.0% at 3 a.m. against 0.2% β€” while the morning commute is under-represented. A bike taken in the small hours by someone without an account is the typical case.

3. Where they start

rate = (cl.assign(lost=at_cap[cl.index]).groupby("start_station_name")
        .agg(trips=("ride_id", "size"), lost=("lost", "sum")))
rate["per 1,000"] = 1000 * rate.lost / rate.trips
busy = rate[rate.trips >= 2000]
print(f"{len(busy)} stations with 2,000+ classic trips; per 1,000 trips: median {busy['per 1,000'].median():.2f}")
print(busy.nlargest(5, "per 1,000").round(1).to_string())
325 stations with 2,000+ classic trips; per 1,000 trips: median 1.21
                                                   trips  lost  per 1,000
start_station_name                                                       
East Falls Church Metro / Sycamore St & 19th St N   2302    34       14.8
Market Square / King St & Royal St                  2091    22       10.5
Rhode Island Ave Metro                              2172    19        8.7
Fort Totten Metro                                   3128    21        6.7
Prince St & Union St                                2165    13        6.0

The median busy station loses 1.21 rentals per 1,000 classic trips to the cap. Five stand out, at five to twelve times that rate. Three of them are Metro stations β€” East Falls Church, Rhode Island Avenue and Fort Totten β€” and two are on the Old Town Alexandria waterfront. The file cannot say why; a bike left at a Metro station by someone who took the train onward, or ridden out of the service area, would both look like this.

Map of Capital Bikeshare stations with 2,000 or more classic-bike trips, coloured by rentals closed at 25 hours per 1,000 trips. Most are grey to light orange; the highest rates are labelled: East Falls Church Metro at 14.8, Market Square in Alexandria at 10.5 and Rhode Island Ave Metro at 8.7. Map of Capital Bikeshare stations with 2,000 or more classic-bike trips, coloured by rentals closed at 25 hours per 1,000 trips. Most are grey to light orange; the highest rates are labelled: East Falls Church Metro at 14.8, Market Square in Alexandria at 10.5 and Rhode Island Ave Metro at 8.7.
Where the bikes that never came back were taken from, 2025. The colour scale stops at 8 so the ordinary stations stay distinguishable.

4. When in the year

by_month = pd.DataFrame({"closed at cap": lost.groupby(lost.started_at.dt.month).size(),
                         "classic trips": cl.groupby(cl.started_at.dt.month).size()})
by_month["per 1,000"] = (1000 * by_month["closed at cap"] / by_month["classic trips"]).round(2)
print(by_month.T.to_string())
started_at           1          2          3          4          5          6          7          8         9          10         11        12
closed at cap    185.00     138.00     324.00     435.00     579.00     632.00     700.00     479.00     382.0     342.00     197.00    146.00
classic trips  78950.00  112441.00  235004.00  265335.00  268054.00  274015.00  266437.00  248793.00  224059.0  214619.00  144672.00  80343.00
per 1,000          2.34       1.23       1.38       1.64       2.16       2.31       2.63       1.93       1.7       1.59       1.36      1.82

In absolute numbers the capped rentals follow riding β€” 700 in July, 138 in February β€” but per 1,000 trips they also rise in summer, to 2.63 in July from 1.23 in February. January's 2.34 is a winter exception on small numbers.

5. E-bikes stop at eight hours, with a position

ecap = t[at_cap & (t.rideable_type == "electric_bike")]
print(f"{len(ecap):,} e-bike rentals closed at 480 min; with an end position: {ecap.end_lat.notna().mean():.0%}; "
      f"ending at a station: {ecap.end_station_id.notna().mean():.0%}")
print(f"start and end in the same 0.01Β° cell: {((ecap.start_lat.round(2) == ecap.end_lat.round(2)) & (ecap.start_lng.round(2) == ecap.end_lng.round(2))).mean():.0%}")
56 e-bike rentals closed at 480 min; with an end position: 100%; ending at a station: 0%
start and end in the same 0.01Β° cell: 23%

None of the 56 e-bikes closed at eight hours was at a station when the rental closed, and only 23% were in the same 0.01Β° cell they started in. They were ridden somewhere and left, which is what the shorter cap is for: a dockless bike cannot wait 25 hours for a dock to notice it.

Keep them out of averages, not out of the data

The data used on this page

Capital Bikeshare system data, the twelve monthly trip files for 2025. Only the 36 trips that started in 2024 and the 35 with negative durations (see the daylight-saving night) are removed; everything else is kept, because the long tail is the subject.

from pathlib import Path
import zipfile

import numpy as np
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
MONTHS = [f"2025{m:02d}" for m in range(1, 13)]
for m in MONTHS:
    f = Path(f"data/{m}-capitalbikeshare-tripdata.zip")
    if not f.exists():
        f.write_bytes(requests.get(f"https://s3.amazonaws.com/capitalbikeshare-data/{f.name}", timeout=600).content)


def read_month(m):
    with zipfile.ZipFile(f"data/{m}-capitalbikeshare-tripdata.zip") as z, z.open(f"{m}-capitalbikeshare-tripdata.csv") as fh:
        return pd.read_csv(fh, dtype={"start_station_id": "Int64", "end_station_id": "Int64"},
                           parse_dates=["started_at", "ended_at"])


t = pd.concat([read_month(m) for m in MONTHS], ignore_index=True)
t["minutes"] = (t.ended_at - t.started_at).dt.total_seconds() / 60
t = t[(t.started_at >= "2025-01-01") & (t.minutes >= 0)].reset_index(drop=True)
print(f"{len(t):,} trips")
6,662,576 trips

Example 1: what capped rentals do to a station's mean

st = cl.dropna(subset=["start_station_name"]).assign(capped=at_cap[cl.index])
g = st.groupby("start_station_name")
means = pd.DataFrame({"trips": g.size(), "mean, all": g.minutes.mean(),
                      "mean, without capped": st[~st.capped].groupby("start_station_name").minutes.mean(),
                      "median": g.minutes.median()})
means = means[means.trips >= 2000]
print(means.nlargest(5, "mean, all").round(1).to_string())
print(f"stations whose rank by mean duration moves 50+ places: "
      f"{(means['mean, all'].rank() - means['mean, without capped'].rank()).abs().ge(50).sum()} of {len(means)}")
                                                   trips  mean, all  mean, without capped  median
start_station_name                                                                               
Prince St & Union St                                2165       49.3                  40.5    25.6
Mt Vernon Trail & S. Washington St.                 2170       45.6                  39.5    23.9
19th St & Constitution Ave NW                       4929       42.2                  36.9    23.0
Market Square / King St & Royal St                  2091       42.1                  26.6    10.9
East Falls Church Metro / Sycamore St & 19th St N   2302       41.9                  20.1    10.0
stations whose rank by mean duration moves 50+ places: 24 of 325

Station-level means are where capped rentals do their damage. At Market Square in Old Town Alexandria the mean classic trip is 42.1 minutes with them and 26.6 without, while the median is 10.9; at East Falls Church Metro, 41.9 against 20.1. Across the 325 busy stations, 24 move 50 or more places in a ranking by mean trip length when the capped rentals are removed. A ranking by median is barely affected.

Dot plot for the twelve stations whose mean classic-bike trip length changes most when capped rentals are removed: the mean with them, the mean without them and the median. At East Falls Church Metro the mean falls from about 42 to 20 minutes while the median is about 10. Dot plot for the twelve stations whose mean classic-bike trip length changes most when capped rentals are removed: the mean with them, the mean without them and the median. At East Falls Church Metro the mean falls from about 42 to 20 minutes while the median is about 10.
Mean against median at the stations most affected. The capped rentals move the mean; the median barely notices them.

Example 2: separate the capped rentals instead of dropping them

t["ended_as"] = np.select([at_cap & t.end_lat.isna(), at_cap], ["unreturned, closed at cap", "closed at cap"], "returned")
print(t.groupby(["rideable_type", "ended_as"]).minutes.agg(["size", "median"]).round(1).to_string())
                                            size  median
rideable_type ended_as                                  
classic_bike  closed at cap                  129  1499.9
              returned                   2408183    10.0
              unreturned, closed at cap     4410  1499.9
electric_bike closed at cap                   56   479.9
              returned                   4249798     8.9

A labelled column keeps the rows for what they are good for β€” counting lost bikes by station and season β€” while every duration statistic is computed on ended_as == "returned". The median returned trip is 10.0 minutes on a classic bike and 8.9 on an e-bike; the capped ones sit at 1,499.9 and 479.9.

A duration that measures the system, not the ride

Every trip duration is the time between two events recorded by the system: an unlock and a lock. When the second event never happens, the system eventually invents one, and the duration becomes a property of the system's rules rather than of anyone's ride. That is why the capped rentals cluster on exact values, why the value differs between bike types, and why it differs between cities using the same file format (five systems compared).

Treating them as outliers to trim β€” the top 1% of durations, say β€” would be the wrong tool. A percentile cut-off removes real long rides along with them and moves whenever the share of lost bikes changes. Identifying them by the rule that created them removes exactly them, and leaves a column that can be counted on its own: lost bikes per station is an operational measure an operator cares about, not noise.

Reading the long tail

  • Find the cap per bike type with a histogram of the tail; do not assume 24 hours.
  • A capped rental can have an end position if the bike was docked after the cap. Use the duration, not the missing position, to find them.
  • The daylight-saving night adds or removes an hour from rentals that span it.
  • Use medians for station comparisons, or remove capped rentals before taking means.
  • A trip without an end position is not always capped. 85 classic-bike trips here ended within the cap with no position.

FAQ

Why do some bike-share trips last exactly 25 hours?

Because the system closes a classic-bike rental that has not been returned after 1,500 minutes. In Capital Bikeshare's 2025 data, 4,539 rentals were closed this way, 4,410 of them with no end position.

How long can a Capital Bikeshare e-bike rental last?

No 2025 e-bike trip ran past 480 minutes; 56 were closed at that limit, all with an end position and none at a station.