defmodule Explorer.Backend.Series do
@moduledoc """
The behaviour for series backends.
"""
@type t :: struct()
@type s :: Explorer.Series.t()
@type lazy_s :: Explorer.Series.lazy_t()
@type df :: Explorer.DataFrame.t()
@type dtype :: Explorer.Series.dtype()
@type valid_types ::
number()
| boolean()
| String.t()
| Date.t()
| Time.t()
| NaiveDateTime.t()
| Explorer.Duration.t()
@type non_finite :: Explorer.Series.non_finite()
@type option(type) :: type | nil
# Conversion
@callback from_list(list(), dtype()) :: s
@callback from_binary(binary(), dtype()) :: s
@callback to_list(s) :: list()
@callback to_iovec(s) :: [binary()]
@callback cast(s, dtype) :: s
@callback categorise(s, s) :: s
@callback strptime(s, String.t()) :: s
@callback strftime(s, String.t()) :: s
# Introspection
@callback dtype(s) :: dtype()
@callback size(s) :: non_neg_integer() | lazy_s()
@callback inspect(s, opts :: Inspect.Opts.t()) :: Inspect.Algebra.t()
@callback categories(s) :: s
# Slice and dice
@callback head(s, n :: integer()) :: s
@callback tail(s, n :: integer()) :: s
@callback sample(
s,
n_or_frac :: number(),
replacement :: boolean(),
shuffle :: boolean(),
seed :: option(integer())
) :: s
@callback at(s, idx :: integer()) :: s
@callback at_every(s, integer()) :: s
@callback mask(s, mask :: s) :: s
@callback slice(s, indices :: list() | s()) :: s
@callback slice(s, offset :: integer(), length :: integer()) :: s
@callback format(list(s)) :: s
@callback concat(list(s)) :: s
@callback coalesce(s, s) :: s
@callback first(s) :: valid_types() | lazy_s()
@callback last(s) :: valid_types() | lazy_s()
@callback select(predicate :: s, s, s) :: s
@callback shift(s, offset :: integer, default :: nil) :: s
@callback rank(
s,
method :: atom(),
descending :: boolean(),
seed :: option(integer())
) :: s | lazy_s()
# Aggregation
@callback count(s) :: number() | lazy_s()
@callback sum(s) :: number() | non_finite() | lazy_s() | nil
@callback min(s) :: number() | non_finite() | Date.t() | NaiveDateTime.t() | lazy_s() | nil
@callback max(s) :: number() | non_finite() | Date.t() | NaiveDateTime.t() | lazy_s() | nil
@callback argmin(s) :: number() | non_finite() | lazy_s() | nil
@callback argmax(s) :: number() | non_finite() | lazy_s() | nil
@callback mean(s) :: float() | non_finite() | lazy_s() | nil
@callback median(s) :: float() | non_finite() | lazy_s() | nil
@callback mode(s) :: s | lazy_s()
@callback variance(s, ddof :: non_neg_integer()) :: float() | non_finite() | lazy_s() | nil
@callback standard_deviation(s, ddof :: non_neg_integer()) ::
float() | non_finite() | lazy_s() | nil
@callback quantile(s, float()) ::
number() | non_finite() | Date.t() | NaiveDateTime.t() | lazy_s() | nil
@callback nil_count(s) :: number() | lazy_s()
@callback product(s) :: float() | non_finite() | lazy_s() | nil
@callback skew(s, bias? :: boolean()) :: float() | non_finite() | lazy_s() | nil
@callback correlation(s, s, ddof :: non_neg_integer(), method :: atom()) ::
float() | non_finite() | lazy_s() | nil
@callback covariance(s, s, ddof :: non_neg_integer()) :: float() | non_finite() | lazy_s() | nil
@callback all?(s) :: boolean() | lazy_s()
@callback any?(s) :: boolean() | lazy_s()
@callback row_index(s) :: s | lazy_s()
# Cumulative
@callback cumulative_max(s, reverse? :: boolean()) :: s
@callback cumulative_min(s, reverse? :: boolean()) :: s
@callback cumulative_sum(s, reverse? :: boolean()) :: s
@callback cumulative_product(s, reverse? :: boolean()) :: s
# Local minima/maxima
@callback peaks(s, :max | :min) :: s
# Arithmetic
@callback add(out_dtype :: dtype(), s, s) :: s
@callback subtract(out_dtype :: dtype(), s, s) :: s
@callback multiply(out_dtype :: dtype(), s, s) :: s
@callback divide(out_dtype :: dtype(), s, s) :: s
@callback quotient(s, s) :: s
@callback remainder(s, s) :: s
@callback pow(out_dtype :: dtype(), s, s) :: s
@callback log(argument :: s) :: s
@callback log(argument :: s, base :: float()) :: s
@callback exp(s) :: s
@callback abs(s) :: s
@callback clip(s, number(), number()) :: s
# Trigonometry
@callback acos(s) :: s
@callback asin(s) :: s
@callback atan(s) :: s
@callback cos(s) :: s
@callback sin(s) :: s
@callback tan(s) :: s
# Comparisons
@callback equal(s, s) :: s
@callback not_equal(s, s) :: s
@callback greater(s, s) :: s
@callback greater_equal(s, s) :: s
@callback less(s, s) :: s
@callback less_equal(s, s) :: s
@callback all_equal(s, s) :: boolean() | lazy_s()
@callback binary_and(s, s) :: s
@callback binary_or(s, s) :: s
@callback binary_in(s, s) :: s
# Float predicates
@callback is_finite(s) :: s
@callback is_infinite(s) :: s
@callback is_nan(s) :: s
# Float round
@callback round(s, decimals :: non_neg_integer()) :: s
@callback floor(s) :: s
@callback ceil(s) :: s
# Coercion
# Sort
@callback sort(
s,
descending? :: boolean(),
maintain_order? :: boolean(),
multithreaded? :: boolean(),
nulls_last? :: boolean()
) :: s
@callback argsort(
s,
descending? :: boolean(),
maintain_order? :: boolean(),
multithreaded? :: boolean(),
nulls_last? :: boolean()
) :: s
@callback reverse(s) :: s
# Distinct
@callback distinct(s) :: s
@callback unordered_distinct(s) :: s
@callback n_distinct(s) :: integer() | lazy_s()
@callback frequencies(s) :: df
# Categorisation
@callback cut(s, [float()], [String.t()] | nil, String.t() | nil, String.t() | nil) ::
df
@callback qcut(s, [float()], [String.t()] | nil, String.t() | nil, String.t() | nil) ::
df
# Rolling
@callback window_sum(
s,
window_size :: integer(),
weights :: [float()] | nil,
min_periods :: integer() | nil,
center :: boolean()
) :: s
@callback window_min(
s,
window_size :: integer(),
weights :: [float()] | nil,
min_periods :: integer() | nil,
center :: boolean()
) :: s
@callback window_max(
s,
window_size :: integer(),
weights :: [float()] | nil,
min_periods :: integer() | nil,
center :: boolean()
) :: s
@callback window_mean(
s,
window_size :: integer(),
weights :: [float()] | nil,
min_periods :: integer() | nil,
center :: boolean()
) :: s
@callback window_median(
s,
window_size :: integer(),
weights :: [float()] | nil,
min_periods :: integer() | nil,
center :: boolean()
) :: s
@callback window_standard_deviation(
s,
window_size :: integer(),
weights :: [float()] | nil,
min_periods :: integer() | nil,
center :: boolean()
) :: s
# Exponentially weighted windows
@callback ewm_mean(
s,
alpha :: float(),
adjust :: boolean(),
min_periods :: integer(),
ignore_nils :: boolean()
) :: s
@callback ewm_standard_deviation(
s,
alpha :: float(),
adjust :: boolean(),
bias :: boolean(),
min_periods :: integer(),
ignore_nils :: boolean()
) :: s
@callback ewm_variance(
s,
alpha :: float(),
adjust :: boolean(),
bias :: boolean(),
min_periods :: integer(),
ignore_nils :: boolean()
) :: s
# Nulls
@callback fill_missing_with_strategy(s, :backward | :forward | :min | :max | :mean) :: s
@callback fill_missing_with_value(s, :nan | valid_types()) :: s
@callback is_nil(s) :: s
@callback is_not_nil(s) :: s
# Escape hatch
@callback transform(s, fun) :: s
# Inversions
@callback unary_not(s) :: s
# Strings
@callback contains(s, String.t()) :: s
@callback upcase(s) :: s
@callback downcase(s) :: s
@callback replace(s, String.t(), String.t()) :: s
@callback strip(s, String.t() | nil) :: s
@callback lstrip(s, String.t() | nil) :: s
@callback rstrip(s, String.t() | nil) :: s
@callback substring(s, integer(), non_neg_integer() | nil) :: s
@callback split(s, String.t()) :: s
@callback split_into(s, String.t(), list(String.t() | atom())) :: s
@callback json_decode(s, dtype()) :: s
@callback json_path_match(s, String.t()) :: s
@callback count_matches(s, String.t()) :: s
## String - Regular expression versions
@callback re_contains(s, String.t()) :: s
@callback re_replace(s, String.t(), String.t()) :: s
@callback re_count_matches(s, String.t()) :: s
@callback re_scan(s, String.t()) :: s
@callback re_named_captures(s, String.t()) :: s
# Date / DateTime
@callback day_of_week(s) :: s
@callback day_of_year(s) :: s
@callback week_of_year(s) :: s
@callback month(s) :: s
@callback year(s) :: s
@callback hour(s) :: s
@callback minute(s) :: s
@callback second(s) :: s
# List
@callback join(s, String.t()) :: s
@callback lengths(s) :: s
@callback member?(s, valid_types()) :: s
# Struct
@callback field(s, String.t()) :: s
# Functions
@doc """
Create a new `Series`.
"""
def new(data, dtype) do
dtype = Explorer.Shared.normalise_dtype!(dtype)
%Explorer.Series{data: data, dtype: dtype}
end
alias Inspect.Algebra, as: A
alias Explorer.Series
@doc """
Default inspect implementation for backends.
"""
def inspect(series, backend, n_rows, inspect_opts, opts \\ [])
when is_binary(backend) and (is_integer(n_rows) or is_nil(n_rows) or is_binary(n_rows)) and
is_list(opts) do
elide_columns? = Keyword.get(opts, :elide_columns, false)
open = A.color("[", :list, inspect_opts)
close = A.color("]", :list, inspect_opts)
type =
series
|> Series.dtype()
|> Explorer.Shared.dtype_to_string()
dtype = A.color("#{type} ", :atom, inspect_opts)
data = build_series_data(series, inspect_opts, elide_columns?)
A.concat([
A.color(backend, :atom, inspect_opts),
open,
"#{n_rows || "???"}",
close,
A.line(),
dtype,
data
])
end
defp build_series_data(_series, _inspect_opts, true) do
"???"
end
defp build_series_data(series, inspect_opts, _elide_columns?) do
series =
case inspect_opts.limit do
:infinity -> series
limit when is_integer(limit) -> Series.slice(series, 0, limit + 1)
end
series
|> Series.to_list()
|> Explorer.Shared.to_doc(inspect_opts)
end
end