Bot Statistics

Analysis of OpenStreetMap contributions from automated bots.

Monthly Bot Activity Trends

Analysis of monthly total, percentage, and accumulated bot edits and contributors.

code
# Monthly bot statistics with totals, percentages, and accumulated values
df = duckdb.sql("""
WITH monthly_total AS (
    SELECT 
        year,
        month,
        CONCAT(year, '-', LPAD(CAST(month as VARCHAR), 2, '0')) as months,
        COUNT(DISTINCT user_name) as total_contributors,
        CAST(SUM(edit_count) as BIGINT) as total_edits
    FROM '../changeset_data/year=*/month=*/*.parquet'
    GROUP BY year, month
),
monthly_bot AS (
    SELECT 
        year,
        month,
        CONCAT(year, '-', LPAD(CAST(month as VARCHAR), 2, '0')) as months,
        COUNT(DISTINCT user_name) as bot_contributors,
        CAST(SUM(edit_count) as BIGINT) as bot_edits
    FROM '../changeset_data/year=*/month=*/*.parquet'
    WHERE bot = true
    GROUP BY year, month
)
SELECT 
    mt.months,
    mt.year,
    mt.month,
    COALESCE(mb.bot_contributors, 0) as "Bot Contributors",
    COALESCE(mb.bot_edits, 0) as "Bot Edits",
    ROUND((COALESCE(mb.bot_contributors, 0) * 100.0) / mt.total_contributors, 2) as "Percent Bot Contributors",
    ROUND((COALESCE(mb.bot_edits, 0) * 100.0) / mt.total_edits, 2) as "Percent Bot Edits",
FROM monthly_total mt
LEFT JOIN monthly_bot mb ON mt.year = mb.year AND mt.month = mb.month
ORDER BY mt.year, mt.month
""").df()

util.show_figure(
    [
        util.FigureConfig(
            title="Monthly Bot Edits",
            label="Total Edits",
            x_col="months",
            y_col="Bot Edits",
            query_or_df=df,
        ),
        util.FigureConfig(
            title="Percentage of Bot Edits",
            label="Percent Edits",
            x_col="months",
            y_col="Percent Bot Edits",
            y_unit_hover_template="%",
            query_or_df=df,
        ),
        util.FigureConfig(
            title="Monthly Bot Contributors",
            label="Total Contributors",
            x_col="months",
            y_col="Bot Contributors",
            query_or_df=df,
        ),
        util.FigureConfig(
            title="Percentage of Bot Contributors",
            label="Percent Contributors",
            x_col="months",
            y_col="Percent Bot Contributors",
            y_unit_hover_template="%",
            query_or_df=df,
        ),
    ]
)

Yearly Bot Statistics by Editing Software

Interactive table showing yearly edits and contributors for each bot editing software.

code
# Yearly statistics for bot editing software
query = """
WITH user_first_year AS (
    SELECT 
        user_name,
        created_by,
        MIN(year) as first_year
    FROM '../changeset_data/year=*/month=*/*.parquet'
    WHERE created_by IS NOT NULL AND bot = true
    GROUP BY user_name, created_by
),
software_totals AS (
    SELECT
        created_by as "Editing Software",
        CAST(SUM(edit_count) as BIGINT) as total_edits_all_time,
        CAST(SUM(CASE WHEN year >= 2021 THEN edit_count ELSE 0 END) as BIGINT) as total_edits_2021_now,
        CAST(COUNT(DISTINCT user_name) as BIGINT) as total_contributors_all_time,
        CAST(COUNT(DISTINCT CASE WHEN year >= 2021 THEN user_name END) as BIGINT) as total_contributors_2021_now
    FROM '../changeset_data/year=*/month=*/*.parquet'
    WHERE created_by IS NOT NULL AND bot = true
    GROUP BY created_by
),
yearly_metrics AS (
    SELECT
        d.year,
        d.created_by as "Editing Software",
        CAST(SUM(d.edit_count) as BIGINT) as "Edits",
        CAST(COUNT(DISTINCT d.user_name) as BIGINT) as "Contributors",
        CAST(COUNT(DISTINCT CASE WHEN ufy.first_year = d.year THEN d.user_name END) as BIGINT) as "New Contributors"
    FROM '../changeset_data/year=*/month=*/*.parquet' d
    LEFT JOIN user_first_year ufy 
        ON d.user_name = ufy.user_name AND d.created_by = ufy.created_by
    WHERE d.created_by IS NOT NULL AND d.bot = true
    GROUP BY d.year, d.created_by
)
SELECT 
    ym.year,
    ym."Editing Software",
    ym."Edits",
    ym."New Contributors",
    ym."Contributors",
    st.total_edits_all_time as "Total Edits",
    st.total_edits_2021_now as "Total Edits (2021 - Now)",
    st.total_contributors_all_time as "Total Contributors",
    st.total_contributors_2021_now as "Total Contributors (2021 - Now)"
FROM yearly_metrics ym
JOIN software_totals st
    ON ym."Editing Software" = st."Editing Software"
ORDER BY year DESC, "Edits" DESC
"""

df = duckdb.sql(query).df()

# Get top editing software by total edits and contributors
top_edits = df.groupby("Editing Software")["Total Edits"].first().nlargest(100)
top_edits_2021_now = df.groupby("Editing Software")["Total Edits (2021 - Now)"].first().nlargest(100)
top_contributors = df.groupby("Editing Software")["Total Contributors"].first().nlargest(100)
top_contributors_2021_now = df.groupby("Editing Software")["Total Contributors (2021 - Now)"].first().nlargest(100)

table_configs = [
    util.TableConfig(
        title="Top 100 Bot Editing Software by Edits",
        query_or_df=df[df["Editing Software"].isin(top_edits.index)],
        x_axis_col="year",
        y_axis_col="Editing Software",
        value_col="Edits",
        center_columns=["Rank", "Editing Software"],
        sum_col="Total Edits",
    ),
    util.TableConfig(
        title="Top 100 Bot Editing Software by Edits 2021 - Now",
        query_or_df=df[(df["Editing Software"].isin(top_edits_2021_now.index)) & (df["year"] >= 2021)],
        x_axis_col="year",
        y_axis_col="Editing Software",
        value_col="Edits",
        center_columns=["Rank", "Editing Software"],
        sum_col="Total Edits (2021 - Now)",
    ),
    util.TableConfig(
        title="Top 100 Bot Editing Software by Contributors",
        query_or_df=df[df["Editing Software"].isin(top_contributors.index)],
        x_axis_col="year",
        y_axis_col="Editing Software",
        value_col="Contributors",
        center_columns=["Rank", "Editing Software"],
        sum_col="Total Contributors",
    ),
    util.TableConfig(
        title="Top 100 Bot Editing Software by Contributors 2021 - Now",
        query_or_df=df[(df["Editing Software"].isin(top_contributors_2021_now.index)) & (df["year"] >= 2021)],
        x_axis_col="year",
        y_axis_col="Editing Software",
        value_col="Contributors",
        center_columns=["Rank", "Editing Software"],
        sum_col="Total Contributors (2021 - Now)",
    ),
]

util.show_tables(table_configs)
Rank Editing Software 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 Total Edits
1 osmtools 0 0 488,405 1,199,363 1,241,094 6,820,108 15,029,385 2,655,466 3,271,936 7,558,536 6,454,470 8,151,944 12,399,003 29,128,241 2,154,313 1,546,179 98,098,443
2 Redaction bot 0 0 21,303,266 284,119 102,887 81,142 195,753 278,968 323,820 308,288 3,375,623 33,835 8,164 521,134 1,471 0 26,818,470
3 autoAWS 0 0 0 0 0 0 0 0 2,674,425 76,127 178,939 133,574 2,635,334 87,343 66,735 69,521 5,921,998
4 JOSM 3,943 59,844 602,095 997,656 13,511 3,350 0 134,189 120,598 218 52,828 3,267 2,158,485 285 46,430 20,103 4,216,802
5 Roy 0 0 1,216,065 2,940,171 0 0 0 0 0 0 0 0 0 0 0 0 4,156,236
6 FindvejBot 3,600,197 358,760 104,606 175 0 0 0 0 0 0 0 0 0 0 0 0 4,063,738
7 osmapi 0 0 0 0 71,233 0 556,109 3 15,143 53,062 14,674 281,782 107,001 501,978 789,476 187,369 2,577,830
8 upload.py 2,045,740 0 267 75,897 3,793 2,717 0 0 0 0 0 0 0 0 0 0 2,128,414
9 osmapis 0 0 127,237 343,781 327,188 137,592 307,640 95,443 64,731 0 0 0 0 0 0 0 1,403,612
10 bash script 0 0 0 0 0 0 0 0 0 124 7,548 1,237,987 1 0 80 0 1,245,740
11 https_all_the_things 0 0 0 0 0 0 0 0 0 443,396 11,218 10,430 62,062 365,444 76,678 28,265 997,493
12 bulk_upload.py 0 0 0 0 0 0 785,031 0 0 0 0 0 0 0 0 0 785,031
13 FixKarlsruheSchema 261,188 426,449 3,941 0 0 0 132 0 0 0 0 0 0 0 0 0 691,710
14 OsmPipeline 0 0 0 0 0 0 0 0 0 1,566 650,018 0 0 0 0 0 651,584
15 osmupload.py 0 0 0 433,989 0 16,731 0 0 0 0 0 0 0 0 0 0 450,720
16 FixTypo7 0 207,292 47,914 0 0 0 0 0 0 0 0 0 0 0 0 0 255,206
17 FixDoubleNodes 165,239 76,666 11,259 0 0 0 0 0 0 0 0 0 0 0 0 0 253,164
18 PeoriaJanitor 195,442 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 195,442
19 TrimTags 182,601 10,483 266 0 0 0 0 0 0 0 0 0 0 0 0 0 193,350
20 David Nicholson 0 0 0 0 0 0 0 0 0 190,054 0 0 0 0 0 0 190,054
21 Osmaxil 0 0 0 0 0 49,004 0 118,268 0 0 0 0 0 0 0 0 167,272
22 gtfs2osm v1.0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 74,878 74,878
23 FixStraßeDeAt 20,251 5,988 17,563 0 0 0 5,727 0 0 0 0 0 0 0 0 0 49,529
24 GNU Emacs 0 0 367 26,581 14,424 5,969 0 0 0 0 0 0 0 0 0 0 47,341
25 OSMFixCanada 46,307 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 46,307
26 PythonOsmApi 0 35,843 5,295 5,139 0 0 0 0 0 0 0 0 0 0 0 0 46,277
27 gtfs2osm v0.9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 31,176 475 31,651
28 FixRussianAddress 0 20,634 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20,634
29 railwayz.info photolines engine 0 0 0 0 0 0 0 0 0 14,923 0 0 0 0 0 0 14,923
30 FixTMC 7,591 2,646 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10,237
31 AWSbot 0 0 0 0 0 8,750 0 0 0 0 0 0 0 0 0 0 8,750
32 bulk_upload_sax.py 7,761 0 920 0 0 0 0 0 0 0 0 0 0 0 0 0 8,681
33 MoveSourceKeyToChangeset 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8,359 0 8,359
34 FixTypo6 7,972 175 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8,147
35 FixRomanianDiacritics 1,482 4,729 1,019 0 0 0 699 0 0 0 0 0 0 0 0 0 7,929
36 bash scripts 0 0 0 0 0 0 0 0 0 7,586 0 0 0 0 0 0 7,586
37 rocketdata.io 0 0 0 0 0 0 0 0 5,083 0 0 0 0 0 0 0 5,083
38 ValidatePhoneNumbers 11432ff0dc 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4,153 0 4,153
39 Codingi.be osmapi/4.0.0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4,014 0 4,014
40 treemap/0.1.0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3,526 3,526
41 osmbot 0.1.0 0 0 0 0 0 0 0 0 0 0 2,485 0 0 0 0 0 2,485
42 FixStraßeHyphen 2,423 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2,423
43 0 1,819 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1,819
44 Python/3.9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1,812 1,812
45 FixStrasseCh 535 868 380 0 0 0 0 0 0 0 0 0 0 0 0 0 1,783
46 OSM-Wikipedia bot 152 0 1,536 0 0 0 0 0 0 0 0 0 0 0 0 0 1,688
47 script mairies5962 0 0 0 1,528 0 0 0 0 0 0 0 0 0 0 0 0 1,528
48 FixHighwayRefs 816 593 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1,409
49 R 0.6 0 0 0 0 0 0 0 0 0 0 0 0 0 1,118 0 0 1,118
50 osmuser63783_script 0.1 0 0 0 0 0 0 0 0 0 0 0 0 0 1,050 0 0 1,050
51 IsraelHiking.osm.org.il 0 0 0 0 0 0 0 0 0 0 0 0 0 0 763 0 763
52 Custom Script 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 588 588
53 ValidatePhoneNumbers 1f5a1d42a6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 572 0 572
54 osmfetch 0 393 0 0 0 0 0 0 0 0 0 0 0 0 0 0 393
55 FixEscapes 0 242 14 0 0 0 0 0 0 0 0 0 0 0 0 0 256
56 Everydoorbot 0 0 0 0 0 0 0 0 0 0 0 0 0 0 185 54 239
57 myPyOSM-alpha 0 0 0 221 0 0 0 0 0 0 0 0 0 0 0 0 221
58 小璋流同步機器人 (osmapi/0.6.0) 0 0 0 0 0 212 0 0 0 0 0 0 0 0 0 0 212
59 business.zoon.ru 0 0 0 0 0 0 0 0 0 24 170 0 0 0 0 0 194
60 machina_reparanda 0 0 0 0 0 0 0 22 126 0 0 0 0 0 0 0 148
61 tiger-expansion-bot 0 0 111 0 0 0 0 0 0 0 0 0 0 0 0 0 111
62 https://github.com/OsmSharp/osm-api-client/ 0 0 0 0 0 0 0 0 0 0 9 0 0 0 0 55 64
63 Fixing railways stations data 0 0 0 0 0 0 0 0 0 52 0 0 0 0 0 0 52
64 SearchAroundBot 0 0 0 0 0 0 9 17 5 0 2 6 2 1 2 0 44
65 Kort (osmapi/0.2.23) 0 0 0 0 36 0 0 0 0 0 0 0 0 0 0 0 36
66 FFMChecker 0 0 0 0 0 0 0 0 0 0 0 0 0 0 35 0 35
67 scosmol 0 0 0 32 0 0 0 0 0 0 0 0 0 0 0 0 32
68 BagBot 0 0 0 0 0 0 0 0 0 0 0 0 0 11 0 0 11
69 Kikimora 0 0 0 0 0 4 6 0 0 0 0 0 0 0 0 0 10
70 osm_gobot/0.1.0 0 0 0 0 0 0 0 0 0 0 9 0 0 0 0 0 9
71 update-osm-fips.py 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 5
72 NeisEditSupport v.0.4.0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
73 manchesterclimate.com 1.0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
74 pyxbot 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

Geographic Distribution of Bot Edits

World map showing the total number of edits made by bots across different geographical locations.

code
# Geographic distribution of bot edits
df = duckdb.sql("""
SELECT
    mid_pos_x as x,
    mid_pos_y as y,
    SUM(edit_count) as z
FROM '../changeset_data/year=*/month=*/*.parquet'
WHERE mid_pos_x IS NOT NULL AND mid_pos_y IS NOT NULL AND bot = true
GROUP BY mid_pos_x, mid_pos_y
""").df()

util.show_figure(
    [
        util.FigureConfig(
            title="Geographic Distribution of Bot Edits",
            x_col="x",
            y_col="y",
            z_col="z",
            query_or_df=df,
            plot_type="map",
        )
    ]
)