@log_start_end<EOL>@doc_sub(metric_params, metric_return, cols_note, splits_note)<EOL>def o_ring(patch, cols, splits, spp, bin_edges, density=True, full=False):
try:<EOL><INDENT>geo.box(<NUM_LIT:0>, <NUM_LIT:0>, <NUM_LIT:1>, <NUM_LIT:1>)<EOL><DEDENT>except:<EOL><INDENT>raise ImportError("<STR_LIT>")<EOL><DEDENT>(spp_col, count_col, x_col, y_col), patch =_get_cols(['<STR_LIT>', '<STR_LIT>', '<STR_LIT>', '<STR_LIT>'], cols, patch)<EOL>result_list = []<EOL>for substring, subpatch in _yield_subpatches(patch, splits):<EOL><INDENT>spp_table = subpatch.table[subpatch.table[spp_col] == spp]<EOL>if (len(spp_table) == <NUM_LIT:0>):<EOL><INDENT>result_list.append((substring, pd.DataFrame(columns=['<STR_LIT:x>','<STR_LIT:y>'])))<EOL>continue<EOL><DEDENT>plot_poly, radii, torus_areas =_get_plot_geometry(subpatch, bin_edges, x_col, y_col)<EOL>x = spp_table[x_col]<EOL>y = spp_table[y_col]<EOL>points = list(zip(x,y))<EOL>counts = list(spp_table[count_col])<EOL>if full:<EOL><INDENT>hists = []  <EOL>areas = []<EOL><DEDENT>else:<EOL><INDENT>hists = np.zeros(len(radii))<EOL>areas = np.zeros(len(radii))<EOL><DEDENT>for i, (point, count) in enumerate(zip(points, counts)):<EOL><INDENT>other_points = points[<NUM_LIT:0>:i] + points[i+<NUM_LIT:1>:]<EOL>other_counts = counts[<NUM_LIT:0>:i] + counts[i+<NUM_LIT:1>:]<EOL>if other_points:<EOL><INDENT>other_dists = dist.cdist(np.array([point]),<EOL>np.array(other_points))<EOL><DEDENT>else:<EOL><INDENT>other_dists = np.array(())<EOL><DEDENT>other_dists = np.repeat(other_dists, other_counts)<EOL>other_dists = np.tile(other_dists, count)<EOL>if count > <NUM_LIT:1>:<EOL><INDENT>other_dists = np.concatenate((other_dists,<EOL>np.zeros(count*(count-<NUM_LIT:1>))))<EOL><DEDENT>hist, _ = np.histogram(other_dists, bin_edges)<EOL>corr_factor = np.ones(len(radii))  <EOL>for j, r in enumerate(radii):<EOL><INDENT>circ = geo.Point(*point).buffer(r, resolution=<NUM_LIT:64>)<EOL>outside_len = circ.boundary.difference(plot_poly).length<EOL>corr_factor[j] = ((circ.boundary.length - outside_len) /<EOL>circ.boundary.length)<EOL><DEDENT>if full:<EOL><INDENT>hists.append(hist)<EOL>areas.append(torus_areas * corr_factor * count)<EOL><DEDENT>else:<EOL><INDENT>hists += hist<EOL>areas += torus_areas * corr_factor * count<EOL><DEDENT><DEDENT>if density:<EOL><INDENT>hists = np.array(hists) / np.array(areas)<EOL><DEDENT>subresult = pd.DataFrame({'<STR_LIT:x>': radii})<EOL>if full:<EOL><INDENT>for i in range(len(hists)):<EOL><INDENT>subresult[i] = hists[i]<EOL><DEDENT><DEDENT>else:<EOL><INDENT>subresult['<STR_LIT:y>'] = hists<EOL><DEDENT>result_list.append((substring, subresult))<EOL><DEDENT>return result_list<EOL>
Calculates univariate O-ring for a species

Parameters
----------
{0}
bin_edges : iterable
    List of edges of distance classes to bin histogram of distances
spp : str
    String corresponding to focal species code
density : bool
    If True, return densities (counts divided by area of torus defined
    by bin edges) instead of counts. Default True.
full : bool
    If True, return a separate column giving density at distance x for
    every individual, rather than mean density. Default False.

Returns
-------
{1} Result has two columns, x and y, that give the distance to the center
of a torus and the number or density of individuals found in that torus.

Notes
-----
If density is False, counts are raw counts, non-edge corrected, within
rings.

Pairwise distances are directional, giving n(n-1) total distances for a
species with n individuals, as edge correction is inherently directional.

Bins include the lower edge and exclude the upper edge, except for the
final bin which includes both the lower and upper edge. Floating point
arithmetic may cause points located "exactly" on edges to be allocated
contrary to this rule, however.

If there are no records for a species, result table will be a dataframe
with no records. If there are records but a species has only one
individual, dataframe will have zero count at all torus areas.

When using density, the maximum distance used for edge correction, given by
the mean of the last two bin_edge values, should ideally be set to no
greater than one half the diagonal distance across the plot. This ensures
that it is not possible for an entire edge correction buffer to be outside
of the plot.

{2}

For the 0-ring analysis, cols must also contain x_col and y_col, giving the
x and y dimensions along which to analyze spatial pattern.

{3}