docs: def o_ring(patch, cols, splits, spp, bin_edges, density=True, full=False):
      """
      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}
  
      """
  
      try:
          geo.box(0, 0, 1, 1)
      except:
          raise ImportError, "O-ring analysis requires shapely package"
  
      (spp_col, count_col, x_col, y_col), patch = \
          _get_cols(['spp_col', 'count_col', 'x_col', 'y_col'], cols, patch)
  
      # Loop through each split
      result_list = []
      for substring, subpatch in _yield_subpatches(patch, splits):
  
          # Get table for just this species
          spp_table = subpatch.table[subpatch.table[spp_col] == spp]
  
          # If spp not present, continue
          if (len(spp_table) == 0):
              result_list.append((substring, pd.DataFrame(columns=['x','y'])))
              continue
  
          # Set up plot geometry
          plot_poly, radii, torus_areas = \
              _get_plot_geometry(subpatch, bin_edges, x_col, y_col)
  
          # Get lists of all points and counts in spp_table
          x = spp_table[x_col]
          y = spp_table[y_col]
          points = zip(x,y)
          counts = list(spp_table[count_col])
  
          # Arrays to hold summed areas and distance histograms for all points
  
          if full:
              hists = []  # Vectors of len(radii) appended for each point
              areas = []
          else:
              hists = np.zeros(len(radii))
              areas = np.zeros(len(radii))
  
          # Go through each point and associated count
          for i, (point, count) in enumerate(zip(points, counts)):
  
              # Create list of all other points and counts except this
              other_points = points[0:i] + points[i+1:]
              other_counts = counts[0:i] + counts[i+1:]
  
              # Get dist from this point to all other points
              if other_points:
                  other_dists = dist.cdist(np.array([point]),
                                           np.array(other_points))
              else:
                  other_dists = np.array(())
  
              # Repeat other point distances to acccount for their counts
              other_dists = np.repeat(other_dists, other_counts)
  
              # Repeat entire other_dist array to account for count here
              other_dists = np.tile(other_dists, count)
  
              # Add 0's for count at this point to account for count here
              if count > 1:
                  other_dists = np.concatenate((other_dists,
                                                np.zeros(count*(count-1))))
  
              # Calculate histogram of distances to other points
              hist, _ = np.histogram(other_dists, bin_edges)
  
              # Convert histogram to density if desired
              corr_factor = np.ones(len(radii))  # Frac length in plot
              for j, r in enumerate(radii):
                  circ = geo.Point(*point).buffer(r, resolution=64)
                  outside_len = circ.boundary.difference(plot_poly).length
                  corr_factor[j] = ((circ.boundary.length - outside_len) /
                                     circ.boundary.length)
  
              # Add hist and corrected area for this point to running totals
              if full:
                  hists.append(hist)
                  areas.append(torus_areas * corr_factor * count)
              else:
                  hists += hist
                  areas += torus_areas * corr_factor * count
  
          # If density, divide summed torus counts by summed areas
          if density:
              hists = np.array(hists) / np.array(areas)
  
          # Append subset result
          subresult = pd.DataFrame({'x': radii})
          if full:
              for i in range(len(hists)):
                  subresult[i] = hists[i]
          else:
              subresult['y'] = hists
          result_list.append((substring, subresult))
  
      # Return all results
      return result_list