Binning and calculating a huge range of data
You call con.commit() unnecessarily — there is no change to be committed.  You defined all_rows, but never use it.  But those are just minor inefficiencies.

The point of using a database is that you can ask it to tell you interesting things about your data.  If you are just going to dump out the entire contents of a table for analysis, you might as well use a CSV file.

A query like this would be ideal:

SELECT FLOOR(latitude) AS latitude
     , COUNT(userid) AS message_count
     , COUNT(DISTINCT userid) AS distinct_count
    FROM message
    GROUP BY 1
    ORDER BY 1;


Unfortunately, SQLite lacks a FLOOR() function, so a circumlocution is necessary:

SELECT CASE WHEN latitude = CAST(latitude AS INTEGER) OR latitude &gt;= 0 THEN CAST(latitude AS INTEGER)
            ELSE CAST(latitude - 1 AS INTEGER)
       END AS latitude
     , COUNT(userid) AS message_count
     , COUNT(DISTINCT userid) AS distinct_count
    FROM message
    GROUP BY 1
    ORDER BY 1;


Note that a simple CAST (latitude AS INTEGER) would be inappropriate for histogram binning, as casting truncates towards zero, such that -0.99 &lt; latitude &lt; +0.99 would all map to 0.

It should be trivial to read the results from that query into bin_count_all and bin_count_unique.

Note that to make that query run efficiently, it would be wise to

CREATE INDEX message_latitude_userid ON message (latitude, userid);