If you want the size to scale correctly, and print down to the pixel limit, the dpi and figure size need to be adjusted. Then s will behave correctly, starting with the smallest pixel being s=1. You also probably want to change the marker from the default. Try this:
#divide dpi by four, multiply figsize components by four to keep same area fig, ax = plt.subplots(1,2, figsize=(24,12), dpi=72) ax[0].scatter(X,Y, s=1, marker=".", c='k') ax[1].scatter(X,Y, s=100, marker=".", c='k')
This will make the axes labels smaller, however.
Edit: We can also ensure that the s-value is never sub-minimal-pixel size, as pointed out in comments. Here's a function that allows one to play around with these settings using this:
def scat_rand(size=(24,12), scale=72, smin=1, smax=100, sedge=True, min_pix=1): X = np.random.normal(0,1,5000) Y = np.random.normal(0,1,5000) fig, ax = plt.subplots(1,2, figsize=(size), dpi=scale) slimit = (min_pix * 72 / fig.dpi) ** 2 if smin < slimit + .0001: smin = slimit print(f"smin too small, reset to {slimit}") if sedge: ax[0].scatter(X,Y, s=smin, c='k') ax[1].scatter(X,Y, s=smax, c='k') else: ax[0].scatter(X,Y, s=smin, c='k', edgecolor='none') ax[1].scatter(X,Y, s=smax, c='k', edgecolor='none') plt.suptitle(f"Pixel= {size[0] * scale} x {size[1] * scale}", fontsize=14) plt.show()
Here's a little driver if you want to play around with it:
if __name__ == "__main__": temp = int(input("Enter size(1-24): ")) size = (2 * temp, temp) scale = int(input("Enter dpi scale (72, 144, 288, 576, 1152): ")) limit = (72 / scale) ** 2 smin = float(input(f"Enter smallest point (limit = {limit}): ")) smax = float(input("Enter largest point: ")) edge = input("edgecolor (y/n): ") if edge == 'y': sedge = True else: sedge = False scat_rand(size, scale, smin, smax, sedge)
See also: Relationship between dpi and figure size