This project is based on the Bertrand Paradox. The Bertrand Paradox deals with patterns that develop from sampling points/chords in a circle. The paradox occurs when different sampling methods seem like they should have the same output but don’t. The first method for this project samples two points on the perimeter then connects them to get the line and the midpoint to get the point. The second method samples a radius at a random angle around the circle, then a random distance on that radius for the midpoint. The line at a right angle is the chord. The last method samples a random point at uniform in the circle for the midpoint. Then the line at a right angle to the segment from that point to the center is the chord. Each of these produces a different distribution of lines and points.

The code can be found here. We’ll walk through a subset of it in this post. For each method, we generate some features at random, then get lines and points from them.

## Randomization Methods

For these methods, we’ll build a function called `line_points` that takes in the center and radius of the circle along with the features we randomly generated for each method. The function outputs the two endpoints for the chord and the midpoint. After creating `line_points`, we’ll generate the random features then run them through `pmap_dfr` from the `purrr` package to apply the `line_points` function.

For the following code snippets, `circle_h`, `circle_k`, and `circle_radius` are the circle’s center x coordinate, center y coordinates, and radius.

For method 1, we generate the two points on the perimeter as uniform distances from `0` to `2 * pi`. Then we take each of those and convert them to coordinates based on the circle’s center and radius. Finally, get the midpoint and return.

``````### Transformation
## Parameters
# circle_x is x value of center
# circle_y is y value of center
# point_1 is one point from 0 to 2*pi
# point_2 is one point from 0 to 2*pi
## Returns
# dataframe with
# point_1_x, point_1_y, point_2_x, point_2_y, midpoint_x, midpoint_y
line_points <- function(circle_x, circle_y, circle_radius, point_1, point_2) {
# Get x,y values
point_1_x = circle_x + circle_r * cos(point_1)
point_1_y = circle_y + circle_r * sin(point_1)
point_2_x = circle_x + circle_r * cos(point_2)
point_2_y = circle_y + circle_r * sin(point_2)

# Get midpoints
midpoint_x = (point_1_x + point_2_x) / 2
midpoint_y = (point_1_y + point_2_y) / 2

return(data.frame(point_1_x = point_1_x,
point_1_y = point_1_y,
point_2_x = point_2_x,
point_2_y = point_2_y,
midpoint_x = midpoint_x,
midpoint_y = midpoint_y))
}

##-------------
# Dataset setup
##-------------
lines <- data.frame(line = seq(1, n_lines),
point_1 = runif(n_lines, 0, 2 * pi),
point_2 = runif(n_lines, 0, 2 * pi))

lines <- lines %>%
bind_cols(pmap_dfr(list(circle_x = circle_h,
circle_y = circle_k,
point_1 = .\$point_1,
point_2 = .\$point_2), line_points))``````

For method 2, our `line_points` function needs a radius (random uniform from `0` to `2 * pi`) and the proportionate distance from the center to the perimeter (random uniform from `0` to `1`). The `line_points` function takes those values, converts the proportion to the actual distance, finds the ends of a chord crossing that point if the angle was 0, rotates everything to the appropriate angle, then moves to the circle’s location.

``````### Transformation
## Parameters
# circle_x is x value of center
# circle_y is y value of center
# chord is fraction along the radius
## Returns
# dataframe with
# point_1_x, point_1_y, point_2_x, point_2_y, midpoint_x, midpoint_y
# move out to radius from origin

# https://www.mathsisfun.com/algebra/trig-solving-ssa-triangles.html
# we know one angle is 90*
# one side is chord
new_angle = pi - pi/2 - asin(((chord * circle_radius) *

# now rotate these points around to the radius
x_1_moved = cos(new_angle) * x_1 - sin(new_angle) * y_1
y_1_moved = sin(new_angle) * x_1 + cos(new_angle) * y_1
x_2_moved = cos(-new_angle) * x_2 - sin(-new_angle) * y_2
y_2_moved = sin(-new_angle) * x_2 + cos(-new_angle) * y_2

# move back to where the circle is
x_1_moved = x_1_moved + circle_x
y_1_moved = y_1_moved + circle_y
x_2_moved = x_2_moved + circle_x
y_2_moved = y_2_moved + circle_y

return(data.frame(point_1_x = x_1_moved,
point_1_y = y_1_moved,
point_2_x = x_2_moved,
point_2_y = y_2_moved,
midpoint_x = (x_1_moved + x_2_moved) / 2,
midpoint_y = (y_1_moved + y_2_moved) / 2))
}

##-------------
# Dataset setup
##-------------
lines <- data.frame(line = seq(1, n_lines),
chord = runif(n_lines, 0, 1))
lines <- lines %>%
bind_cols(pmap_dfr(list(circle_x = circle_h,
circle_y = circle_k,
chord = .\$chord), line_points))``````

For method 3, `line_points` now needs a point’s x and y coordinates in the unit circle. We then find the chord by rotating the point to angle 0 then finding the vertical line through that point. The chord’s endpoints are where the vertical line crosses the circle. We then rotate those points back, so their midpoint matches the original point.

``````### Transformation
## Parameters
# circle_x is x value of center
# circle_y is y value of center
# point_x is the x value of the random point
# point_y is the y value of the random point
## Returns
# dataframe with
# point_1_x, point_1_y, point_2_x, point_2_y, midpoint_x, midpoint_y
line_points <- function(circle_x, circle_y, circle_radius, point_x, point_y) {
# move out to radius from origin

# rotate so just dealing with y
flatten_by = atan2(point_y, point_x)

# now rotate these points around to the radius
x_rotated = cos(-flatten_by) * point_x - sin(-flatten_by) * point_y
y_rotated = sin(-flatten_by) * point_x + cos(-flatten_by) * point_y # Should be zero

# find y values
# x^2 + y^2 = r^2
# y^2 = r^2 - x^2
# y = +/- sqrt(r^2 - x^2)

# rotate back
point_1_x = cos(flatten_by) * x_rotated - sin(flatten_by) * y1
point_1_y = sin(flatten_by) * x_rotated + cos(flatten_by) * y1
point_2_x = cos(flatten_by) * x_rotated - sin(flatten_by) * y2
point_2_y = sin(flatten_by) * x_rotated + cos(flatten_by) * y2

# move back to where the circle is
point_1_x = point_1_x + circle_x
point_1_y = point_1_y + circle_y
point_2_x = point_2_x + circle_x
point_2_y = point_2_y + circle_y

return(data.frame(point_1_x = point_1_x,
point_1_y = point_1_y,
point_2_x = point_2_x,
point_2_y = point_2_y,
midpoint_x = (point_1_x + point_2_x) / 2,
midpoint_y = (point_1_y + point_2_y) / 2))
}

##-------------
# Dataset setup
##-------------
lines <- data.frame(line = seq(1, n_lines),
r = sqrt(runif(n_lines, 0, 1)),
theta = runif(n_lines, 0, 2*pi))
lines <- lines %>%
mutate(point_x = r * cos(theta),
point_y = r * sin(theta)) %>%
select(line, point_x, point_y)
lines <- lines %>%
bind_cols(pmap_dfr(list(circle_x = circle_h,
circle_y = circle_k,
point_x = .\$point_x,
point_y = .\$point_y), line_points))``````

Now that we have the basic attributes for the three methods, we can talk about coloring them.

## Color Selection

For the methods, I wanted each one to be colored based on different hues. So, I selected them from a yellow-orange/blue-green/purple-red triad. The colors are sampled from ellipses in the HCL color space. The following images show samples from the same ellipses. The line colors are on top and points on the bottom. Method 1: Colors Method 2: Colors Method 3: Colors

In addition to the lines having a lower base Luminance value, the lines’ ellipses are tilted down towards the center while the points’ slope upwards on the Chroma-Luminance plane for their respective Hue values.    Method 1: Chroma-Luminance Plane Method 2: Chroma-Luminance Plane Method 3: Chroma-Luminance Plane

Each of the lines is closer to the secondary colors in Hue than the points. For the order, the revolve around the color wheel counter-clockwise. We can see this when looking at the Hue-Chroma plane at their respective Luminance values.    Method 1: Hue-Chroma Plane Method 2: Hue-Chroma Plane Method 3: Hue-Chroma Plane

I tried picking the colors by algorithm, but I couldn’t get that to work as well as I wanted. I often ended up with too much brown or blue. So, the base colors are hand-picked.

## Final Output

Now that we can generate the lines/point and colors let’s stick it together. We’ll add diagrams towards the bottom of the images to explain the methods along with borders. The Github repo contains the complete code if you want to see it. The diagrams are affected by the random seed, so any example lines/points can be chosen. I used `grid` for this instead of `ggplot2` because I wanted the points and lines to overlap back and forth. That was easier to do with `grid`. Later I changed and put all the points on top of all the lines, but by then, I had it all done in `grid`. I also found this setup easier to control where to place everything on the page instead of worrying about margins or other artifacts getting in the way.

From here to the end are the final products. Better 18" x 24" jpeg files can be found here. Method 1 Method 2 Method 3