module Polars # A Series represents a single column in a polars DataFrame. class Series include ExprDispatch # @private attr_accessor :_s # Create a new Series. # # @param name [String, Array, nil] # Name of the series. Will be used as a column name when used in a DataFrame. # When not specified, name is set to an empty string. # @param values [Array, nil] # One-dimensional data in various forms. Supported are: Array and Series. # @param dtype [Symbol, nil] # Polars dtype of the Series data. If not specified, the dtype is inferred. # @param strict [Boolean] # Throw error on numeric overflow. # @param nan_to_null [Boolean] # Not used. # @param dtype_if_empty [Symbol, nil] # If no dtype is specified and values contains `nil` or an empty array, # set the Polars dtype of the Series data. If not specified, Float32 is used. # # @example Constructing a Series by specifying name and values positionally: # s = Polars::Series.new("a", [1, 2, 3]) # # @example Notice that the dtype is automatically inferred as a polars Int64: # s.dtype # # => :i64 # # @example Constructing a Series with a specific dtype: # s2 = Polars::Series.new("a", [1, 2, 3], dtype: :f32) # # @example It is possible to construct a Series with values as the first positional argument. This syntax considered an anti-pattern, but it can be useful in certain scenarios. You must specify any other arguments through keywords. # s3 = Polars::Series.new([1, 2, 3]) def initialize(name = nil, values = nil, dtype: nil, strict: true, nan_to_null: false, dtype_if_empty: nil) # Handle case where values are passed as the first argument if !name.nil? && !name.is_a?(String) if values.nil? values = name name = nil else raise ArgumentError, "Series name must be a string." end end name = "" if name.nil? if values.nil? self._s = sequence_to_rbseries(name, [], dtype: dtype, dtype_if_empty: dtype_if_empty) elsif values.is_a?(Series) self._s = series_to_rbseries(name, values) elsif values.is_a?(Range) self._s = Polars.arange( values.first, values.last + (values.exclude_end? ? 0 : 1), step: 1, eager: true, dtype: dtype ) .rename(name, in_place: true) ._s elsif values.is_a?(Array) self._s = sequence_to_rbseries(name, values, dtype: dtype, strict: strict, dtype_if_empty: dtype_if_empty) else raise ArgumentError, "Series constructor called with unsupported type; got #{values.class.name}" end end # @private def self._from_rbseries(s) series = Series.allocate series._s = s series end # Get the data type of this Series. # # @return [Symbol] def dtype _s.dtype end # Get flags that are set on the Series. # # @return [Hash] def flags { "SORTED_ASC" => _s.is_sorted_flag, "SORTED_DESC" => _s.is_sorted_reverse_flag } end # Get the inner dtype in of a List typed Series. # # @return [Symbol] def inner_dtype _s.inner_dtype end # Get the name of this Series. # # @return [String] def name _s.name end # Shape of this Series. # # @return [Array] def shape [_s.len] end # Get the time unit of underlying Datetime Series as `"ns"`, `"us"`, or `"ms"`. # # @return [String] def time_unit _s.time_unit end # Returns a string representing the Series. # # @return [String] def to_s _s.to_s end alias_method :inspect, :to_s # Bitwise AND. # # @return [Series] def &(other) if !other.is_a?(Series) other = Series.new([other]) end Utils.wrap_s(_s.bitand(other._s)) end # Bitwise OR. # # @return [Series] def |(other) if !other.is_a?(Series) other = Series.new([other]) end Utils.wrap_s(_s.bitor(other._s)) end # Bitwise XOR. # # @return [Series] def ^(other) if !other.is_a?(Series) other = Series.new([other]) end Utils.wrap_s(_s.bitxor(other._s)) end # Equal. # # @return [Series] def ==(other) _comp(other, :eq) end # Not equal. # # @return [Series] def !=(other) _comp(other, :neq) end # Greater than. # # @return [Series] def >(other) _comp(other, :gt) end # Less than. # # @return [Series] def <(other) _comp(other, :lt) end # Greater than or equal. # # @return [Series] def >=(other) _comp(other, :gt_eq) end # Less than or equal. # # @return [Series] def <=(other) _comp(other, :lt_eq) end # Performs addition. # # @return [Series] def +(other) _arithmetic(other, :add) end # Performs subtraction. # # @return [Series] def -(other) _arithmetic(other, :sub) end # Performs multiplication. # # @return [Series] def *(other) _arithmetic(other, :mul) end # Performs division. # # @return [Series] def /(other) _arithmetic(other, :div) end # Returns the modulo. # # @return [Series] def %(other) if is_datelike raise ArgumentError, "first cast to integer before applying modulo on datelike dtypes" end _arithmetic(other, :rem) end # Raises to the power of exponent. # # @return [Series] def **(power) if is_datelike raise ArgumentError, "first cast to integer before raising datelike dtypes to a power" end to_frame.select(Polars.col(name).pow(power)).to_series end # Performs negation. # # @return [Series] def -@ 0 - self end # Returns elements of the Series. # # @return [Object] def [](item) if item.is_a?(Integer) return _s.get_idx(item) end if item.is_a?(Range) return Slice.new(self).apply(item) end raise ArgumentError, "Cannot get item of type: #{item.class.name}" end # Sets an element of the Series. # # @return [Object] def []=(key, value) if value.is_a?(Array) if is_numeric || is_datelike set_at_idx(key, value) return end raise ArgumentError, "cannot set Series of dtype: #{dtype} with list/tuple as value; use a scalar value" end if key.is_a?(Series) if key.dtype == :bool self._s = set(key, value)._s elsif key.dtype == :u64 self._s = set_at_idx(key.cast(:u32), value)._s elsif key.dtype == :u32 self._s = set_at_idx(key, value)._s else raise Todo end end if key.is_a?(Array) s = Utils.wrap_s(sequence_to_rbseries("", key, dtype: :u32)) self[s] = value elsif key.is_a?(Integer) # TODO fix # self[[key]] = value set_at_idx(key, value) else raise ArgumentError, "cannot use #{key} for indexing" end end # Return an estimation of the total (heap) allocated size of the Series. # # Estimated size is given in the specified unit (bytes by default). # # This estimation is the sum of the size of its buffers, validity, including # nested arrays. Multiple arrays may share buffers and bitmaps. Therefore, the # size of 2 arrays is not the sum of the sizes computed from this function. In # particular, StructArray's size is an upper bound. # # When an array is sliced, its allocated size remains constant because the buffer # unchanged. However, this function will yield a smaller number. This is because # this function returns the visible size of the buffer, not its total capacity. # # FFI buffers are included in this estimation. # # @param unit ["b", "kb", "mb", "gb", "tb"] # Scale the returned size to the given unit. # # @return [Numeric] # # @example # s = Polars::Series.new("values", 1..1_000_000, dtype: :u32) # s.estimated_size # # => 4000000 # s.estimated_size("mb") # # => 3.814697265625 def estimated_size(unit = "b") sz = _s.estimated_size Utils.scale_bytes(sz, to: unit) end # Compute the square root of the elements. # # @return [Series] def sqrt self**0.5 end # Check if any boolean value in the column is `true`. # # @return [Boolean] def any to_frame.select(Polars.col(name).any).to_series[0] end # Check if all boolean values in the column are `true`. # # @return [Boolean] def all to_frame.select(Polars.col(name).all).to_series[0] end # Compute the logarithm to a given base. # # @param base [Float] # Given base, defaults to `Math::E`. # # @return [Series] def log(base = Math::E) super end # Compute the base 10 logarithm of the input array, element-wise. # # @return [Series] def log10 super end # Compute the exponential, element-wise. # # @return [Series] def exp super end # Create a new Series that copies data from this Series without null values. # # @return [Series] def drop_nulls super end # Drop NaN values. # # @return [Series] def drop_nans super end # Cast this Series to a DataFrame. # # @return [DataFrame] def to_frame Utils.wrap_df(RbDataFrame.new([_s])) end # Quick summary statistics of a series. # # Series with mixed datatypes will return summary statistics for the datatype of # the first value. # # @return [DataFrame] # # @example # series_num = Polars::Series.new([1, 2, 3, 4, 5]) # series_num.describe # # => # # shape: (6, 2) # # ┌────────────┬──────────┐ # # │ statistic ┆ value │ # # │ --- ┆ --- │ # # │ str ┆ f64 │ # # ╞════════════╪══════════╡ # # │ min ┆ 1.0 │ # # ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ # # │ max ┆ 5.0 │ # # ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ # # │ null_count ┆ 0.0 │ # # ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ # # │ mean ┆ 3.0 │ # # ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ # # │ std ┆ 1.581139 │ # # ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤ # # │ count ┆ 5.0 │ # # └────────────┴──────────┘ # # @example # series_str = Polars::Series.new(["a", "a", nil, "b", "c"]) # series_str.describe # # => # # shape: (3, 2) # # ┌────────────┬───────┐ # # │ statistic ┆ value │ # # │ --- ┆ --- │ # # │ str ┆ i64 │ # # ╞════════════╪═══════╡ # # │ unique ┆ 4 │ # # ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┤ # # │ null_count ┆ 1 │ # # ├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┤ # # │ count ┆ 5 │ # # └────────────┴───────┘ def describe if len == 0 raise ArgumentError, "Series must contain at least one value" elsif is_numeric s = cast(:f64) stats = { "min" => s.min, "max" => s.max, "null_count" => s.null_count, "mean" => s.mean, "std" => s.std, "count" => s.len } elsif is_boolean stats = { "sum" => sum, "null_count" => null_count, "count" => len } elsif is_utf8 stats = { "unique" => unique.length, "null_count" => null_count, "count" => len } elsif is_datelike # we coerce all to string, because a polars column # only has a single dtype and dates: datetime and count: int don't match stats = { "min" => dt.min.to_s, "max" => dt.max.to_s, "null_count" => null_count.to_s, "count" => len.to_s } else raise TypeError, "This type is not supported" end Polars::DataFrame.new( {"statistic" => stats.keys, "value" => stats.values} ) end # Reduce this Series to the sum value. # # @return [Numeric] # # @note # Dtypes `:i8`, `:u8`, `:i16`, and `:u16` are cast to # `:i64` before summing to prevent overflow issues. # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.sum # # => 6 def sum _s.sum end # Reduce this Series to the mean value. # # @return [Float, nil] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.mean # # => 2.0 def mean _s.mean end # Reduce this Series to the product value. # # @return [Numeric] def product to_frame.select(Polars.col(name).product).to_series[0] end # Get the minimal value in this Series. # # @return [Object] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.min # # => 1 def min _s.min end # Get the maximum value in this Series. # # @return [Object] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.max # # => 3 def max _s.max end # Get maximum value, but propagate/poison encountered NaN values. # # @return [Object] def nan_max to_frame.select(Polars.col(name).nan_max)[0, 0] end # Get minimum value, but propagate/poison encountered NaN values. # # @return [Object] def nan_min to_frame.select(Polars.col(name).nan_min)[0, 0] end # Get the standard deviation of this Series. # # @param ddof [Integer] # “Delta Degrees of Freedom”: the divisor used in the calculation is N - ddof, # where N represents the number of elements. # # @return [Float, nil] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.std # # => 1.0 def std(ddof: 1) if !is_numeric nil else to_frame.select(Polars.col(name).std(ddof: ddof)).to_series[0] end end # Get variance of this Series. # # @param ddof [Integer] # “Delta Degrees of Freedom”: the divisor used in the calculation is N - ddof, # where N represents the number of elements. # # @return [Float, nil] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.var # # => 1.0 def var(ddof: 1) if !is_numeric nil else to_frame.select(Polars.col(name).var(ddof: ddof)).to_series[0] end end # Get the median of this Series. # # @return [Float, nil] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.median # # => 2.0 def median _s.median end # Get the quantile value of this Series. # # @param quantile [Float, nil] # Quantile between 0.0 and 1.0. # @param interpolation ["nearest", "higher", "lower", "midpoint", "linear"] # Interpolation method. # # @return [Float, nil] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.quantile(0.5) # # => 2.0 def quantile(quantile, interpolation: "nearest") _s.quantile(quantile, interpolation) end # Get dummy variables. # # @return [DataFrame] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.to_dummies # # => # # shape: (3, 3) # # ┌─────┬─────┬─────┐ # # │ a_1 ┆ a_2 ┆ a_3 │ # # │ --- ┆ --- ┆ --- │ # # │ u8 ┆ u8 ┆ u8 │ # # ╞═════╪═════╪═════╡ # # │ 1 ┆ 0 ┆ 0 │ # # ├╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌┤ # # │ 0 ┆ 1 ┆ 0 │ # # ├╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌┤ # # │ 0 ┆ 0 ┆ 1 │ # # └─────┴─────┴─────┘ def to_dummies Utils.wrap_df(_s.to_dummies) end # Count the unique values in a Series. # # @param sort [Boolean] # Ensure the output is sorted from most values to least. # # @return [DataFrame] # # @example # s = Polars::Series.new("a", [1, 2, 2, 3]) # s.value_counts.sort("a") # # => # # shape: (3, 2) # # ┌─────┬────────┐ # # │ a ┆ counts │ # # │ --- ┆ --- │ # # │ i64 ┆ u32 │ # # ╞═════╪════════╡ # # │ 1 ┆ 1 │ # # ├╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ # # │ 2 ┆ 2 │ # # ├╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤ # # │ 3 ┆ 1 │ # # └─────┴────────┘ def value_counts(sort: false) Utils.wrap_df(_s.value_counts(sort)) end # Return a count of the unique values in the order of appearance. # # @return [Series] # # @example # s = Polars::Series.new("id", ["a", "b", "b", "c", "c", "c"]) # s.unique_counts # # => # # shape: (3,) # # Series: 'id' [u32] # # [ # # 1 # # 2 # # 3 # # ] def unique_counts super end # Computes the entropy. # # Uses the formula `-sum(pk * log(pk)` where `pk` are discrete probabilities. # # @param base [Float] # Given base, defaults to `e` # @param normalize [Boolean] # Normalize pk if it doesn't sum to 1. # # @return [Float, nil] # # @example # a = Polars::Series.new([0.99, 0.005, 0.005]) # a.entropy(normalize: true) # # => 0.06293300616044681 # # @example # b = Polars::Series.new([0.65, 0.10, 0.25]) # b.entropy(normalize: true) # # => 0.8568409950394724 def entropy(base: Math::E, normalize: false) Polars.select(Polars.lit(self).entropy(base: base, normalize: normalize)).to_series[0] end # def cumulative_eval # end # Return a copy of the Series with a new alias/name. # # @param name [String] # New name. # # @return [Series] # # @example # s = Polars::Series.new("x", [1, 2, 3]) # s.alias("y") def alias(name) s = dup s._s.rename(name) s end # Rename this Series. # # @param name [String] # New name. # @param in_place [Boolean] # Modify the Series in-place. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.rename("b") def rename(name, in_place: false) if in_place _s.rename(name) self else self.alias(name) end end # Get the length of each individual chunk. # # @return [Array] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s2 = Polars::Series.new("b", [4, 5, 6]) # # @example Concatenate Series with rechunk: true # Polars.concat([s, s2]).chunk_lengths # # => [6] # # @example Concatenate Series with rechunk: false # Polars.concat([s, s2], rechunk: false).chunk_lengths # # => [3, 3] def chunk_lengths _s.chunk_lengths end # Get the number of chunks that this Series contains. # # @return [Integer] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s2 = Polars::Series.new("b", [4, 5, 6]) # # @example Concatenate Series with rechunk: true # Polars.concat([s, s2]).n_chunks # # => 1 # # @example Concatenate Series with rechunk: false # Polars.concat([s, s2], rechunk: false).n_chunks # # => 2 def n_chunks _s.n_chunks end # Get an array with the cumulative sum computed at every element. # # @param reverse [Boolean] # reverse the operation. # # @return [Series] # # @note # Dtypes `:i8`, `:u8`, `:i16`, and `:u16` are cast to # `:i64` before summing to prevent overflow issues. # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.cumsum # # => # # shape: (3,) # # Series: 'a' [i64] # # [ # # 1 # # 3 # # 6 # # ] def cumsum(reverse: false) super end # Get an array with the cumulative min computed at every element. # # @param reverse [Boolean] # reverse the operation. # # @return [Series] # # @example # s = Polars::Series.new("a", [3, 5, 1]) # s.cummin # # => # # shape: (3,) # # Series: 'a' [i64] # # [ # # 3 # # 3 # # 1 # # ] def cummin(reverse: false) super end # Get an array with the cumulative max computed at every element. # # @param reverse [Boolean] # reverse the operation. # # @return [Series] # # @example # s = Polars::Series.new("a", [3, 5, 1]) # s.cummax # # => # # shape: (3,) # # Series: 'a' [i64] # # [ # # 3 # # 5 # # 5 # # ] def cummax(reverse: false) super end # Get an array with the cumulative product computed at every element. # # @param reverse [Boolean] # reverse the operation. # # @return [Series] # # @note # Dtypes `:i8`, `:u8`, `:i16`, and `:u16` are cast to # `:i64` before multiplying to prevent overflow issues. # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.cumprod # # => # # shape: (3,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # 6 # # ] def cumprod(reverse: false) super end # Get the first `n` rows. # # Alias for {#head}. # # @param n [Integer] # Number of rows to return. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.limit(2) # # => # # shape: (2,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # ] def limit(n = 10) to_frame.select(Utils.col(name).limit(n)).to_series end # Get a slice of this Series. # # @param offset [Integer] # Start index. Negative indexing is supported. # @param length [Integer, nil] # Length of the slice. If set to `nil`, all rows starting at the offset # will be selected. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3, 4]) # s.slice(1, 2) # # => # # shape: (2,) # # Series: 'a' [i64] # # [ # # 2 # # 3 # # ] def slice(offset, length = nil) super end # Append a Series to this one. # # @param other [Series] # Series to append. # @param append_chunks [Boolean] # If set to `true` the append operation will add the chunks from `other` to # self. This is super cheap. # # If set to `false` the append operation will do the same as # {DataFrame#extend} which extends the memory backed by this Series with # the values from `other`. # # Different from `append_chunks`, `extend` appends the data from `other` to # the underlying memory locations and thus may cause a reallocation (which is # expensive). # # If this does not cause a reallocation, the resulting data structure will not # have any extra chunks and thus will yield faster queries. # # Prefer `extend` over `append_chunks` when you want to do a query after a # single append. For instance during online operations where you add `n` rows # and rerun a query. # # Prefer `append_chunks` over `extend` when you want to append many times # before doing a query. For instance, when you read in multiple files and when # to store them in a single Series. In the latter case, finish the sequence # of `append_chunks` operations with a `rechunk`. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s2 = Polars::Series.new("b", [4, 5, 6]) # s.append(s2) # # => # # shape: (6,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # 3 # # 4 # # 5 # # 6 # # ] def append(other, append_chunks: true) begin if append_chunks _s.append(other._s) else _s.extend(other._s) end rescue => e if e.message == "Already mutably borrowed" append(other.clone, append_chunks) else raise e end end self end # Filter elements by a boolean mask. # # @param predicate [Series, Array] # Boolean mask. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # mask = Polars::Series.new("", [true, false, true]) # s.filter(mask) # # => # # shape: (2,) # # Series: 'a' [i64] # # [ # # 1 # # 3 # # ] def filter(predicate) if predicate.is_a?(Array) predicate = Series.new("", predicate) end Utils.wrap_s(_s.filter(predicate._s)) end # Get the first `n` rows. # # @param n [Integer] # Number of rows to return. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.head(2) # # => # # shape: (2,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # ] def head(n = 10) to_frame.select(Utils.col(name).head(n)).to_series end # Get the last `n` rows. # # @param n [Integer] # Number of rows to return. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.tail(2) # # => # # shape: (2,) # # Series: 'a' [i64] # # [ # # 2 # # 3 # # ] def tail(n = 10) to_frame.select(Utils.col(name).tail(n)).to_series end # Take every nth value in the Series and return as new Series. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3, 4]) # s.take_every(2) # # => # # shape: (2,) # # Series: 'a' [i64] # # [ # # 1 # # 3 # # ] def take_every(n) super end # Sort this Series. # # @param reverse [Boolean] # Reverse sort. # @param in_place [Boolean] # Sort in place. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 3, 4, 2]) # s.sort # # => # # shape: (4,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # 3 # # 4 # # ] # s.sort(reverse: true) # # => # # shape: (4,) # # Series: 'a' [i64] # # [ # # 4 # # 3 # # 2 # # 1 # # ] def sort(reverse: false, in_place: false) if in_place self._s = _s.sort(reverse) self else Utils.wrap_s(_s.sort(reverse)) end end # Return the `k` largest elements. # # If `reverse: true`, the smallest elements will be given. # # @param k [Integer] # Number of elements to return. # @param reverse [Boolean] # Return the smallest elements. # # @return [Boolean] def top_k(k: 5, reverse: false) super end # Get the index values that would sort this Series. # # @param reverse [Boolean] # Sort in reverse (descending) order. # @param nulls_last [Boolean] # Place null values last instead of first. # # @return [Series] # # @example # s = Polars::Series.new("a", [5, 3, 4, 1, 2]) # s.arg_sort # # => # # shape: (5,) # # Series: 'a' [u32] # # [ # # 3 # # 4 # # 1 # # 2 # # 0 # # ] def arg_sort(reverse: false, nulls_last: false) super end # Get the index values that would sort this Series. # # Alias for {#arg_sort}. # # @param reverse [Boolean] # Sort in reverse (descending) order. # @param nulls_last [Boolean] # Place null values last instead of first. # # @return [Series] def argsort(reverse: false, nulls_last: false) super end # Get unique index as Series. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 2, 3]) # s.arg_unique # # => # # shape: (3,) # # Series: 'a' [u32] # # [ # # 0 # # 1 # # 3 # # ] def arg_unique super end # Get the index of the minimal value. # # @return [Integer, nil] # # @example # s = Polars::Series.new("a", [3, 2, 1]) # s.arg_min # # => 2 def arg_min _s.arg_min end # Get the index of the maximal value. # # @return [Integer, nil] # # @example # s = Polars::Series.new("a", [3, 2, 1]) # s.arg_max # # => 0 def arg_max _s.arg_max end # Find indices where elements should be inserted to maintain order. # # @param element [Object] # Expression or scalar value. # # @return [Integer] def search_sorted(element) Polars.select(Polars.lit(self).search_sorted(element))[0, 0] end # Get unique elements in series. # # @param maintain_order [Boolean] # Maintain order of data. This requires more work. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 2, 3]) # s.unique.sort # # => # # shape: (3,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # 3 # # ] def unique(maintain_order: false) super end # Take values by index. # # @param indices [Array] # Index location used for selection. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3, 4]) # s.take([1, 3]) # # => # # shape: (2,) # # Series: 'a' [i64] # # [ # # 2 # # 4 # # ] def take(indices) to_frame.select(Polars.col(name).take(indices)).to_series end # Count the null values in this Series. # # @return [Integer] def null_count _s.null_count end # Return `true` if the Series has a validity bitmask. # # If there is none, it means that there are no null values. # Use this to swiftly assert a Series does not have null values. # # @return [Boolean] def has_validity _s.has_validity end # Check if the Series is empty. # # @return [Boolean] # # @example # s = Polars::Series.new("a", []) # s.is_empty # # => true def is_empty len == 0 end alias_method :empty?, :is_empty # Returns a boolean Series indicating which values are null. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 2.0, 3.0, nil]) # s.is_null # # => # # shape: (4,) # # Series: 'a' [bool] # # [ # # false # # false # # false # # true # # ] def is_null super end # Returns a boolean Series indicating which values are not null. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 2.0, 3.0, nil]) # s.is_not_null # # => # # shape: (4,) # # Series: 'a' [bool] # # [ # # true # # true # # true # # false # # ] def is_not_null super end # Returns a boolean Series indicating which values are finite. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 2.0, Float::INFINITY]) # s.is_finite # # => # # shape: (3,) # # Series: 'a' [bool] # # [ # # true # # true # # false # # ] def is_finite super end # Returns a boolean Series indicating which values are infinite. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 2.0, Float::INFINITY]) # s.is_infinite # # => # # shape: (3,) # # Series: 'a' [bool] # # [ # # false # # false # # true # # ] def is_infinite super end # Returns a boolean Series indicating which values are NaN. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 2.0, 3.0, Float::NAN]) # s.is_nan # # => # # shape: (4,) # # Series: 'a' [bool] # # [ # # false # # false # # false # # true # # ] def is_nan super end # Returns a boolean Series indicating which values are not NaN. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 2.0, 3.0, Float::NAN]) # s.is_not_nan # # => # # shape: (4,) # # Series: 'a' [bool] # # [ # # true # # true # # true # # false # # ] def is_not_nan super end # def is_in # end # Get index values where Boolean Series evaluate `true`. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # (s == 2).arg_true # # => # # shape: (1,) # # Series: 'a' [u32] # # [ # # 1 # # ] def arg_true Polars.arg_where(self, eager: true) end # Get mask of all unique values. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 2, 3]) # s.is_unique # # => # # shape: (4,) # # Series: 'a' [bool] # # [ # # true # # false # # false # # true # # ] def is_unique super end # Get a mask of the first unique value. # # @return [Series] def is_first super end # Get mask of all duplicated values. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 2, 3]) # s.is_duplicated # # => # # shape: (4,) # # Series: 'a' [bool] # # [ # # false # # true # # true # # false # # ] def is_duplicated super end # Explode a list or utf8 Series. # # This means that every item is expanded to a new row. # # @return [Series] # # @example # s = Polars::Series.new("a", [[1, 2], [3, 4], [9, 10]]) # s.explode # # => # # shape: (6,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # 3 # # 4 # # 9 # # 10 # # ] def explode super end # Check if series is equal with another Series. # # @param other [Series] # Series to compare with. # @param null_equal [Boolean] # Consider null values as equal. # @param strict [Boolean] # Don't allow different numerical dtypes, e.g. comparing `:u32` with a # `:i64` will return `false`. # # @return [Boolean] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s2 = Polars::Series.new("b", [4, 5, 6]) # s.series_equal(s) # # => true # s.series_equal(s2) # # => false def series_equal(other, null_equal: false, strict: false) _s.series_equal(other._s, null_equal, strict) end # Length of this Series. # # @return [Integer] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.len # # => 3 def len _s.len end alias_method :length, :len # Cast between data types. # # @param dtype [Symbol] # DataType to cast to # @param strict [Boolean] # Throw an error if a cast could not be done for instance due to an overflow # # @return [Series] # # @example # s = Polars::Series.new("a", [true, false, true]) # s.cast(:u32) # # => # # shape: (3,) # # Series: 'a' [u32] # # [ # # 1 # # 0 # # 1 # # ] def cast(dtype, strict: true) super end # def to_physical # end # Convert this Series to a Ruby Array. This operation clones data. # # @return [Array] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.to_a # # => [1, 2, 3] def to_a _s.to_a end # Create a single chunk of memory for this Series. # # @param in_place [Boolean] # In place or not. # # @return [Series] def rechunk(in_place: false) opt_s = _s.rechunk(in_place) in_place ? self : Utils.wrap_s(opt_s) end # Return Series in reverse order. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3], dtype: :i8) # s.reverse # # => # # shape: (3,) # # Series: 'a' [i8] # # [ # # 3 # # 2 # # 1 # # ] def reverse super end # Check if this Series datatype is numeric. # # @return [Boolean] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.is_numeric # # => true def is_numeric [:i8, :i16, :i32, :i64, :u8, :u16, :u32, :u64, :f32, :f64].include?(dtype) end alias_method :numeric?, :is_numeric # Check if this Series datatype is datelike. # # @return [Boolean] # # @example # s = Polars::Series.new([Date.new(2021, 1, 1), Date.new(2021, 1, 2), Date.new(2021, 1, 3)]) # s.is_datelike # # => true def is_datelike [:date, :datetime, :duration, :time].include?(dtype) end # Check if this Series has floating point numbers. # # @return [Boolean] # # @example # s = Polars::Series.new("a", [1.0, 2.0, 3.0]) # s.is_float # # => true def is_float [:f32, :f64].include?(dtype) end alias_method :float?, :is_float # Check if this Series is a Boolean. # # @return [Boolean] # # @example # s = Polars::Series.new("a", [true, false, true]) # s.is_boolean # # => true def is_boolean dtype == :bool end alias_method :boolean?, :is_boolean alias_method :is_bool, :is_boolean alias_method :bool?, :is_boolean # Check if this Series datatype is a Utf8. # # @return [Boolean] # # @example # s = Polars::Series.new("x", ["a", "b", "c"]) # s.is_utf8 # # => true def is_utf8 dtype == :str end alias_method :utf8?, :is_utf8 # def view # end # def to_numo # end # def set # end # Set values at the index locations. # # @param idx [Object] # Integers representing the index locations. # @param value [Object] # Replacement values. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.set_at_idx(1, 10) # # => # # shape: (3,) # # Series: 'a' [i64] # # [ # # 1 # # 10 # # 3 # # ] def set_at_idx(idx, value) if idx.is_a?(Integer) idx = [idx] end if idx.length == 0 return self end idx = Series.new("", idx) if value.is_a?(Integer) || value.is_a?(Float) || Utils.bool?(value) || value.is_a?(String) || value.nil? value = Series.new("", [value]) # if we need to set more than a single value, we extend it if idx.length > 0 value = value.extend_constant(value[0], idx.length - 1) end elsif !value.is_a?(Series) value = Series.new("", value) end _s.set_at_idx(idx._s, value._s) self end # Create an empty copy of the current Series. # # The copy has identical name/dtype but no data. # # @return [Series] # # @example # s = Polars::Series.new("a", [nil, true, false]) # s.cleared # # => # # shape: (0,) # # Series: 'a' [bool] # # [ # # ] def cleared len > 0 ? limit(0) : clone end # clone handled by initialize_copy # Fill floating point NaN value with a fill value. # # @param fill_value [Object] # Value used to fill nan values. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 2.0, 3.0, Float::NAN]) # s.fill_nan(0) # # => # # shape: (4,) # # Series: 'a' [f64] # # [ # # 1.0 # # 2.0 # # 3.0 # # 0.0 # # ] def fill_nan(fill_value) super end # Fill null values using the specified value or strategy. # # @param value [Object] # Value used to fill null values. # @param strategy [nil, "forward", "backward", "min", "max", "mean", "zero", "one"] # Strategy used to fill null values. # @param limit # Number of consecutive null values to fill when using the "forward" or # "backward" strategy. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3, nil]) # s.fill_null(strategy: "forward") # # => # # shape: (4,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # 3 # # 3 # # ] # # @example # s.fill_null(strategy: "min") # # => # # shape: (4,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # 3 # # 1 # # ] # # @example # s = Polars::Series.new("b", ["x", nil, "z"]) # s.fill_null(Polars.lit("")) # # => # # shape: (3,) # # Series: 'b' [str] # # [ # # "x" # # "" # # "z" # # ] def fill_null(value = nil, strategy: nil, limit: nil) super end # Rounds down to the nearest integer value. # # Only works on floating point Series. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.12345, 2.56789, 3.901234]) # s.floor # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 1.0 # # 2.0 # # 3.0 # # ] def floor Utils.wrap_s(_s.floor) end # Rounds up to the nearest integer value. # # Only works on floating point Series. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.12345, 2.56789, 3.901234]) # s.ceil # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 2.0 # # 3.0 # # 4.0 # # ] def ceil super end # Round underlying floating point data by `decimals` digits. # # @param decimals [Integer] # number of decimals to round by. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.12345, 2.56789, 3.901234]) # s.round(2) # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 1.12 # # 2.57 # # 3.9 # # ] def round(decimals = 0) super end # def dot # end # Compute the most occurring value(s). # # Can return multiple Values. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 2, 3]) # s.mode # # => # # shape: (1,) # # Series: 'a' [i64] # # [ # # 2 # # ] def mode super end # Compute the element-wise indication of the sign. # # @return [Series] # # @example # s = Polars::Series.new("a", [-9.0, -0.0, 0.0, 4.0, nil]) # s.sign # # => # # shape: (5,) # # Series: 'a' [i64] # # [ # # -1 # # 0 # # 0 # # 1 # # null # # ] def sign super end # Compute the element-wise value for the sine. # # @return [Series] # # @example # s = Polars::Series.new("a", [0.0, Math::PI / 2.0, Math::PI]) # s.sin # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 0.0 # # 1.0 # # 1.2246e-16 # # ] def sin super end # Compute the element-wise value for the cosine. # # @return [Series] # # @example # s = Polars::Series.new("a", [0.0, Math::PI / 2.0, Math::PI]) # s.cos # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 1.0 # # 6.1232e-17 # # -1.0 # # ] def cos super end # Compute the element-wise value for the tangent. # # @return [Series] # # @example # s = Polars::Series.new("a", [0.0, Math::PI / 2.0, Math::PI]) # s.tan # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 0.0 # # 1.6331e16 # # -1.2246e-16 # # ] def tan super end # Compute the element-wise value for the inverse sine. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 0.0, -1.0]) # s.arcsin # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 1.570796 # # 0.0 # # -1.570796 # # ] def arcsin super end # Compute the element-wise value for the inverse cosine. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 0.0, -1.0]) # s.arccos # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 0.0 # # 1.570796 # # 3.141593 # # ] def arccos super end # Compute the element-wise value for the inverse tangent. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 0.0, -1.0]) # s.arctan # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 0.785398 # # 0.0 # # -0.785398 # # ] def arctan super end # Compute the element-wise value for the inverse hyperbolic sine. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 0.0, -1.0]) # s.arcsinh # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 0.881374 # # 0.0 # # -0.881374 # # ] def arcsinh super end # Compute the element-wise value for the inverse hyperbolic cosine. # # @return [Series] # # @example # s = Polars::Series.new("a", [5.0, 1.0, 0.0, -1.0]) # s.arccosh # # => # # shape: (4,) # # Series: 'a' [f64] # # [ # # 2.292432 # # 0.0 # # NaN # # NaN # # ] def arccosh super end # Compute the element-wise value for the inverse hyperbolic tangent. # # @return [Series] # # @example # s = Polars::Series.new("a", [2.0, 1.0, 0.5, 0.0, -0.5, -1.0, -1.1]) # s.arctanh # # => # # shape: (7,) # # Series: 'a' [f64] # # [ # # NaN # # inf # # 0.549306 # # 0.0 # # -0.549306 # # -inf # # NaN # # ] def arctanh super end # Compute the element-wise value for the hyperbolic sine. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 0.0, -1.0]) # s.sinh # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 1.175201 # # 0.0 # # -1.175201 # # ] def sinh super end # Compute the element-wise value for the hyperbolic cosine. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 0.0, -1.0]) # s.cosh # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 1.543081 # # 1.0 # # 1.543081 # # ] def cosh super end # Compute the element-wise value for the hyperbolic tangent. # # @return [Series] # # @example # s = Polars::Series.new("a", [1.0, 0.0, -1.0]) # s.tanh # # => # # shape: (3,) # # Series: 'a' [f64] # # [ # # 0.761594 # # 0.0 # # -0.761594 # # ] def tanh super end # def apply # end # Shift the values by a given period. # # @param periods [Integer] # Number of places to shift (may be negative). # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.shift(1) # # => # # shape: (3,) # # Series: 'a' [i64] # # [ # # null # # 1 # # 2 # # ] # # @example # s.shift(-1) # # => # # shape: (3,) # # Series: 'a' [i64] # # [ # # 2 # # 3 # # null # # ] def shift(periods = 1) super end # Shift the values by a given period and fill the resulting null values. # # @param periods [Integer] # Number of places to shift (may be negative). # @param fill_value [Object] # Fill None values with the result of this expression. # # @return [Series] def shift_and_fill(periods, fill_value) super end # Take values from self or other based on the given mask. # # Where mask evaluates true, take values from self. Where mask evaluates false, # take values from other. # # @param mask [Series] # Boolean Series. # @param other [Series] # Series of same type. # # @return [Series] # # @example # s1 = Polars::Series.new([1, 2, 3, 4, 5]) # s2 = Polars::Series.new([5, 4, 3, 2, 1]) # s1.zip_with(s1 < s2, s2) # # => # # shape: (5,) # # Series: '' [i64] # # [ # # 1 # # 2 # # 3 # # 2 # # 1 # # ] # # @example # mask = Polars::Series.new([true, false, true, false, true]) # s1.zip_with(mask, s2) # # => # # shape: (5,) # # Series: '' [i64] # # [ # # 1 # # 4 # # 3 # # 2 # # 5 # # ] def zip_with(mask, other) Utils.wrap_s(_s.zip_with(mask._s, other._s)) end # def rolling_min # end # def rolling_max # end # def rolling_mean # end # def rolling_sum # end # def rolling_std # end # def rolling_var # end # def rolling_apply # end # def rolling_median # end # def rolling_quantile # end # def rolling_skew # end # def sample # end # Get a boolean mask of the local maximum peaks. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3, 4, 5]) # s.peak_max # # => # # shape: (5,) # # Series: '' [bool] # # [ # # false # # false # # false # # false # # true # # ] def peak_max Utils.wrap_s(_s.peak_max) end # Get a boolean mask of the local minimum peaks. # # @return [Series] # # @example # s = Polars::Series.new("a", [4, 1, 3, 2, 5]) # s.peak_min # # => # # shape: (5,) # # Series: '' [bool] # # [ # # false # # true # # false # # true # # false # # ] def peak_min Utils.wrap_s(_s.peak_min) end # Count the number of unique values in this Series. # # @return [Integer] # # @example # s = Polars::Series.new("a", [1, 2, 2, 3]) # s.n_unique # # => 3 def n_unique _s.n_unique end # Shrink Series memory usage. # # Shrinks the underlying array capacity to exactly fit the actual data. # (Note that this function does not change the Series data type). # # @return [Series] def shrink_to_fit(in_place: false) if in_place _s.shrink_to_fit self else series = clone series._s.shrink_to_fit series end end # def _hash # end # Reinterpret the underlying bits as a signed/unsigned integer. # # This operation is only allowed for 64bit integers. For lower bits integers, # you can safely use that cast operation. # # @param signed [Boolean] # If true, reinterpret as `:i64`. Otherwise, reinterpret as `:u64`. # # @return [Series] def reinterpret(signed: true) super end # Interpolate intermediate values. The interpolation method is linear. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, nil, nil, 5]) # s.interpolate # # => # # shape: (5,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # 3 # # 4 # # 5 # # ] def interpolate super end # Compute absolute values. # # @return [Series] def abs super end # def rank # end # Calculate the n-th discrete difference. # # @param n [Integer] # Number of slots to shift. # @param null_behavior ["ignore", "drop"] # How to handle null values. # # @return [Series] def diff(n: 1, null_behavior: "ignore") super end # def pct_change # end # Compute the sample skewness of a data set. # # For normally distributed data, the skewness should be about zero. For # unimodal continuous distributions, a skewness value greater than zero means # that there is more weight in the right tail of the distribution. The # function `skewtest` can be used to determine if the skewness value # is close enough to zero, statistically speaking. # # @param bias [Boolean] # If `false`, the calculations are corrected for statistical bias. # # @return [Float, nil] def skew(bias: true) _s.skew(bias) end # Compute the kurtosis (Fisher or Pearson) of a dataset. # # Kurtosis is the fourth central moment divided by the square of the # variance. If Fisher's definition is used, then 3.0 is subtracted from # the result to give 0.0 for a normal distribution. # If bias is false, then the kurtosis is calculated using k statistics to # eliminate bias coming from biased moment estimators # # @param fisher [Boolean] # If `true`, Fisher's definition is used (normal ==> 0.0). If `false`, # Pearson's definition is used (normal ==> 3.0). # @param bias [Boolean] # If `false`, the calculations are corrected for statistical bias. # # @return [Float, nil] def kurtosis(fisher: true, bias: true) _s.kurtosis(fisher, bias) end # Clip (limit) the values in an array to a `min` and `max` boundary. # # Only works for numerical types. # # If you want to clip other dtypes, consider writing a "when, then, otherwise" # expression. See {#when} for more information. # # @param min_val [Numeric] # Minimum value. # @param max_val [Numeric] # Maximum value. # # @return [Series] # # @example # s = Polars::Series.new("foo", [-50, 5, nil, 50]) # s.clip(1, 10) # # => # # shape: (4,) # # Series: 'foo' [i64] # # [ # # 1 # # 5 # # null # # 10 # # ] def clip(min_val, max_val) super end # Clip (limit) the values in an array to a `min` boundary. # # Only works for numerical types. # # If you want to clip other dtypes, consider writing a "when, then, otherwise" # expression. See {#when} for more information. # # @param min_val [Numeric] # Minimum value. # # @return [Series] def clip_min(min_val) super end # Clip (limit) the values in an array to a `max` boundary. # # Only works for numerical types. # # If you want to clip other dtypes, consider writing a "when, then, otherwise" # expression. See {#when} for more information. # # @param max_val [Numeric] # Maximum value. # # @return [Series] def clip_max(max_val) super end # Reshape this Series to a flat Series or a Series of Lists. # # @param dims [Array] # Tuple of the dimension sizes. If a -1 is used in any of the dimensions, that # dimension is inferred. # # @return [Series] def reshape(dims) super end # Shuffle the contents of this Series. # # @param seed [Integer, nil] # Seed for the random number generator. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.shuffle(seed: 1) # # => # # shape: (3,) # # Series: 'a' [i64] # # [ # # 2 # # 1 # # 3 # # ] def shuffle(seed: nil) super end # def ewm_mean # end # def ewm_std # end # def ewm_var # end # Extend the Series with given number of values. # # @param value [Object] # The value to extend the Series with. This value may be `nil` to fill with # nulls. # @param n [Integer] # The number of values to extend. # # @return [Series] # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.extend_constant(99, 2) # # => # # shape: (5,) # # Series: 'a' [i64] # # [ # # 1 # # 2 # # 3 # # 99 # # 99 # # ] def extend_constant(value, n) super end # Flags the Series as sorted. # # Enables downstream code to user fast paths for sorted arrays. # # @param reverse [Boolean] # If the Series order is reversed, e.g. descending. # # @return [Series] # # @note # This can lead to incorrect results if this Series is not sorted!! # Use with care! # # @example # s = Polars::Series.new("a", [1, 2, 3]) # s.set_sorted.max # # => 3 def set_sorted(reverse: false) Utils.wrap_s(_s.set_sorted(reverse)) end # Create a new Series filled with values from the given index. # # @return [Series] def new_from_index(index, length) Utils.wrap_s(_s.new_from_index(index, length)) end # Shrink numeric columns to the minimal required datatype. # # Shrink to the dtype needed to fit the extrema of this Series. # This can be used to reduce memory pressure. # # @return [Series] def shrink_dtype super end # def arr # end # def cat # end # def dt # end # def str # end # def struct # end private def initialize_copy(other) super self._s = _s._clone end def coerce(other) if other.is_a?(Numeric) # TODO improve series = to_frame.select(Polars.lit(other)).to_series [series, self] else raise TypeError, "#{self.class} can't be coerced into #{other.class}" end end def _comp(other, op) if other.is_a?(Series) return Utils.wrap_s(_s.send(op, other._s)) end if dtype == :str raise Todo end Utils.wrap_s(_s.send("#{op}_#{dtype}", other)) end def _arithmetic(other, op) if other.is_a?(Expr) other = to_frame.select(other).to_series end if other.is_a?(Series) return Utils.wrap_s(_s.send(op, other._s)) end raise Todo end def series_to_rbseries(name, values) # should not be in-place? values.rename(name, in_place: true) values._s end def sequence_to_rbseries(name, values, dtype: nil, strict: true, dtype_if_empty: nil) ruby_dtype = nil nested_dtype = nil if (values.nil? || values.empty?) && dtype.nil? if dtype_if_empty # if dtype for empty sequence could be guessed # (e.g comparisons between self and other) dtype = dtype_if_empty else # default to Float32 type dtype = :f32 end end rb_temporal_types = [] rb_temporal_types << Date if defined?(Date) rb_temporal_types << DateTime if defined?(DateTime) rb_temporal_types << Time if defined?(Time) value = _get_first_non_none(values) if !dtype.nil? && Utils.is_polars_dtype(dtype) && ruby_dtype.nil? constructor = polars_type_to_constructor(dtype) rbseries = constructor.call(name, values, strict) return rbseries else if ruby_dtype.nil? if value.nil? # generic default dtype ruby_dtype = Float else ruby_dtype = value.class end end # temporal branch if rb_temporal_types.include?(ruby_dtype) # if dtype.nil? # dtype = rb_type_to_dtype(ruby_dtype) # elsif rb_temporal_types.include?(dtype) # dtype = rb_type_to_dtype(dtype) # end if ruby_dtype == Date RbSeries.new_opt_date(name, values, strict) else raise Todo end elsif ruby_dtype == Array if nested_dtype.nil? nested_value = _get_first_non_none(value) nested_dtype = nested_value.nil? ? Float : nested_value.class end if nested_dtype == Array raise Todo end raise Todo else constructor = rb_type_to_constructor(value.class) constructor.call(name, values, strict) end end end POLARS_TYPE_TO_CONSTRUCTOR = { f32: RbSeries.method(:new_opt_f32), f64: RbSeries.method(:new_opt_f64), i8: RbSeries.method(:new_opt_i8), i16: RbSeries.method(:new_opt_i16), i32: RbSeries.method(:new_opt_i32), i64: RbSeries.method(:new_opt_i64), u8: RbSeries.method(:new_opt_u8), u16: RbSeries.method(:new_opt_u16), u32: RbSeries.method(:new_opt_u32), u64: RbSeries.method(:new_opt_u64), bool: RbSeries.method(:new_opt_bool), str: RbSeries.method(:new_str) } def polars_type_to_constructor(dtype) POLARS_TYPE_TO_CONSTRUCTOR.fetch(dtype.to_sym) rescue KeyError raise ArgumentError, "Cannot construct RbSeries for type #{dtype}." end RB_TYPE_TO_CONSTRUCTOR = { Float => RbSeries.method(:new_opt_f64), Integer => RbSeries.method(:new_opt_i64), String => RbSeries.method(:new_str), TrueClass => RbSeries.method(:new_opt_bool), FalseClass => RbSeries.method(:new_opt_bool) } def rb_type_to_constructor(dtype) RB_TYPE_TO_CONSTRUCTOR.fetch(dtype) rescue KeyError # RbSeries.method(:new_object) raise ArgumentError, "Cannot determine type" end def _get_first_non_none(values) values.find { |v| !v.nil? } end end end