By clusters, I mean groups of linked overlapping circles. This image probably gives a better idea of what I’m trying to find:

In my data, circles are represented by their centerpoint coordinates. I have already done collision detection to produce a list of paired centered points representing the overlaps:
pts = [(-2,2), (-2,2), (0,0), (2,1), (6,2), (7,1)]
overlaps = [
(pts[0], pts[1]),
(pts[0], pts[2]),
(pts[1], pts[2]),
(pts[2], pts[3]),
(pts[4], pts[5]),
]
This is the expected result:
expected_clusters = [
((-2,2), (-2,2), (0,0), (2,1)),
((6,2), (7,1))
]
In practice, the datasets I will be working with will be about this size so I’ll probably never need to scale it up. But that’s not to say I wouldn’t favor a more optimal solution.
I’ve come up with my own naive solution, which I’ll post as an answer. But I’d be interested in seeing other solutions.
What you’re doing isn’t really cluster analysis, it’s connected component analysis. Cluster analysis would be taking a whole bunch of individual points and trying to discover the circles. But it may be of interest to you that the combination of assigning points into initial neighborhoods and clustering based reachability through overlapping neighborhoods is the heart of the DBSCAN idea and its density-based clustering variants.
In any case, since you’re starting with the circles, once you’ve done the collision detection, what you’re calling your overlaps list is the adjacency list, and what you’re calling the clusters are the connected components. The algorithm is fairly straightforward:
Lof all nodes.CsLis not empty:NC, initialized withNCCtoCsCfromL