From 29d72c11c69cf25c75eb372b69c2303d1027e98d Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Wed, 1 Mar 2023 13:59:24 -0500 Subject: [PATCH 1/4] DRAFT: basic fix manually specifying imports --- siuba/__init__.py | 30 +++++++++++++++++++++++++++--- siuba/dply/verbs.py | 10 ++++------ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/siuba/__init__.py b/siuba/__init__.py index 39cca46b..54a77cc8 100644 --- a/siuba/__init__.py +++ b/siuba/__init__.py @@ -4,8 +4,32 @@ # default imports-------------------------------------------------------------- from .siu import _, Fx, Lam from .dply.across import across -from .dply.verbs import * -from .dply.verbs import __all__ as ALL_DPLY +from .dply.verbs import ( + # Dply ---- + group_by, ungroup, + select, rename, + mutate, transmute, filter, summarize, + arrange, distinct, + count, add_count, + head, + top_n, + # Tidy ---- + spread, gather, + nest, unnest, + expand, complete, + separate, unite, extract, + # Joins ---- + join, inner_join, full_join, left_join, right_join, semi_join, anti_join, + # TODO: move to vectors + if_else, case_when, + collect, show_query, + tbl, + pipe +) # necessary, since _ won't be exposed in import * by default -__all__ = ['_', "Fx", "across", *ALL_DPLY] +__all__ = [ + '_', + "Fx", + "across", + ] diff --git a/siuba/dply/verbs.py b/siuba/dply/verbs.py index 9ff7545a..bf255d92 100644 --- a/siuba/dply/verbs.py +++ b/siuba/dply/verbs.py @@ -16,7 +16,7 @@ from .tidyselect import var_create, var_select, Var -DPLY_FUNCTIONS = ( +__all__ = ( # Dply ---- "group_by", "ungroup", "select", "rename", @@ -35,11 +35,9 @@ # TODO: move to vectors "if_else", "case_when", "collect", "show_query", - "tbl", + "tbl" ) -__all__ = [*DPLY_FUNCTIONS, "Pipeable", "pipe"] - # General TODO ================================================================ # * expressions in group_by @@ -52,7 +50,7 @@ def install_siu_methods(cls): """ func_dict = globals() - for func_name in DPLY_FUNCTIONS: + for func_name in __all__: f = func_dict[func_name] method_name = "siu_{}".format(func_name) @@ -61,7 +59,7 @@ def install_siu_methods(cls): def install_pd_siu(): # https://github.com/coursera/pandas-ply/blob/master/pandas_ply/methods.py func_dict = globals() - for func_name in DPLY_FUNCTIONS: + for func_name in __all__: f = func_dict[func_name] method_name = "siu_{}".format(func_name) From cd839695645258a5b967b87300537257055297a8 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Wed, 1 Mar 2023 17:18:19 -0500 Subject: [PATCH 2/4] DRAFT: more init import tweaking --- siuba/__init__.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/siuba/__init__.py b/siuba/__init__.py index 54a77cc8..7661b4f0 100644 --- a/siuba/__init__.py +++ b/siuba/__init__.py @@ -2,7 +2,7 @@ __version__ = "0.4.2" # default imports-------------------------------------------------------------- -from .siu import _, Fx, Lam +from .siu import _, Fx, Lam, pipe from .dply.across import across from .dply.verbs import ( # Dply ---- @@ -24,12 +24,29 @@ if_else, case_when, collect, show_query, tbl, - pipe ) # necessary, since _ won't be exposed in import * by default __all__ = [ '_', "Fx", + "pipe", "across", - ] + "group_by", "ungroup", + "select", "rename", + "mutate", "transmute", "filter", "summarize", + "arrange", "distinct", + "count", "add_count", + "head", + "top_n", + # Tidy ---- + "spread", "gather", + "nest", "unnest", + "expand", "complete", + "separate", "unite", "extract", + # Joins ---- + "join", "inner_join", "full_join", "left_join", "right_join", "semi_join", "anti_join", + "if_else", "case_when", + "collect", "show_query", + "tbl" +] From efb2ef2ef64ec89e811efadc82c8caf64a6bf894 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Fri, 24 Mar 2023 19:33:01 -0400 Subject: [PATCH 3/4] docs: fix docstrings column operation modules --- siuba/dply/forcats.py | 2 + siuba/dply/vector.py | 293 +++++++++++++++++++++++------------------- siuba/dply/verbs.py | 17 +++ 3 files changed, 183 insertions(+), 129 deletions(-) diff --git a/siuba/dply/forcats.py b/siuba/dply/forcats.py index 621722ac..b439c08c 100644 --- a/siuba/dply/forcats.py +++ b/siuba/dply/forcats.py @@ -1,3 +1,5 @@ +"""Functions for working with categorical column data.""" + import pandas as pd import numpy as np diff --git a/siuba/dply/vector.py b/siuba/dply/vector.py index 6311285f..04cbe6ea 100644 --- a/siuba/dply/vector.py +++ b/siuba/dply/vector.py @@ -1,3 +1,5 @@ +"""Functions that implement dplyr vector operations.""" + import pandas as pd import numpy as np from functools import singledispatch @@ -29,7 +31,7 @@ def _expand_bool(x, f): return x.expanding().apply(f, raw = True).astype(bool) -def alias_series_agg(name): +def _alias_series_agg(name): method = method_agg_op(name, is_property = False, accessor = False) def decorator(dispatcher): @@ -47,12 +49,13 @@ def decorator(dispatcher): def cumall(x): """Return a same-length array. For each entry, indicates whether that entry and all previous are True-like. - Example: - >>> cumall(pd.Series([True, False, False])) - 0 True - 1 False - 2 False - dtype: bool + Examples + -------- + >>> cumall(pd.Series([True, False, False])) + 0 True + 1 False + 2 False + dtype: bool """ return _expand_bool(x, np.all) @@ -64,12 +67,13 @@ def cumall(x): def cumany(x): """Return a same-length array. For each entry, indicates whether that entry or any previous are True-like. - Example: - >>> cumany(pd.Series([False, True, False])) - 0 False - 1 True - 2 True - dtype: bool + Examples + -------- + >>> cumany(pd.Series([False, True, False])) + 0 False + 1 True + 2 True + dtype: bool """ return _expand_bool(x, np.any) @@ -110,14 +114,14 @@ def dense_rank(x, na_option = "keep"): This method of ranking returns values ranging from 1 to the number of unique entries. Ties are all given the same ranking. - Example: - - >>> dense_rank(pd.Series([1,3,3,5])) - 0 1.0 - 1 2.0 - 2 2.0 - 3 3.0 - dtype: float64 + Examples + -------- + >>> dense_rank(pd.Series([1,3,3,5])) + 0 1.0 + 1 2.0 + 2 2.0 + 3 3.0 + dtype: float64 """ @@ -130,25 +134,27 @@ def dense_rank(x, na_option = "keep"): def percent_rank(x, na_option = "keep"): """Return the percent rank. - Note: - Uses minimum rank, and reports the proportion of unique ranks each entry is greater than. + Notes + ----- + Uses minimum rank, and reports the proportion of unique ranks each entry is greater than. - Examples: - >>> percent_rank(pd.Series([1, 2, 3])) - 0 0.0 - 1 0.5 - 2 1.0 - dtype: float64 + Examples + -------- + >>> percent_rank(pd.Series([1, 2, 3])) + 0 0.0 + 1 0.5 + 2 1.0 + dtype: float64 - >>> percent_rank(pd.Series([1, 2, 2])) - 0 0.0 - 1 0.5 - 2 0.5 - dtype: float64 + >>> percent_rank(pd.Series([1, 2, 2])) + 0 0.0 + 1 0.5 + 2 0.5 + dtype: float64 - >>> percent_rank(pd.Series([1])) - 0 NaN - dtype: float64 + >>> percent_rank(pd.Series([1])) + 0 NaN + dtype: float64 """ @@ -183,22 +189,23 @@ def cume_dist(x, na_option = "keep"): def row_number(x): """Return the row number (position) for each value in x, beginning with 1. - Example: - >>> ser = pd.Series([7,8]) - >>> row_number(ser) - 0 1 - 1 2 - dtype: int64 + Examples + -------- + >>> ser = pd.Series([7,8]) + >>> row_number(ser) + 0 1 + 1 2 + dtype: int64 - >>> row_number(pd.DataFrame({'a': ser})) - 0 1 - 1 2 - dtype: int64 + >>> row_number(pd.DataFrame({'a': ser})) + 0 1 + 1 2 + dtype: int64 - >>> row_number(pd.Series([7,8], index = [3, 4])) - 3 1 - 4 2 - dtype: int64 + >>> row_number(pd.Series([7,8], index = [3, 4])) + 3 1 + 4 2 + dtype: int64 """ @@ -241,15 +248,18 @@ def ntile(x, n): def between(x, left, right, default = False): """Return whether a value is between left and right (including either side). - Example: - >>> between(pd.Series([1,2,3]), 0, 2) - 0 True - 1 True - 2 False - dtype: bool + Notes + ----- + This is a thin wrapper around pd.Series.between(left, right) + + Examples + -------- + >>> between(pd.Series([1,2,3]), 0, 2) + 0 True + 1 True + 2 False + dtype: bool - Note: - This is a thin wrapper around pd.Series.between(left, right) """ # note: NA -> False, in tidyverse NA -> NA @@ -305,23 +315,28 @@ def coalesce(x, *args): def lead(x, n = 1, default = None): """Return an array with each value replaced by the next (or further forward) value in the array. - Arguments: - x: a pandas Series object - n: number of next values forward to replace each value with - default: what to replace the n final values of the array with - - Example: - >>> lead(pd.Series([1,2,3]), n=1) - 0 2.0 - 1 3.0 - 2 NaN - dtype: float64 - - >>> lead(pd.Series([1,2,3]), n=1, default = 99) - 0 2 - 1 3 - 2 99 - dtype: int64 + Parameters + ---------- + x: + a pandas Series object + n: + number of next values forward to replace each value with + default: + what to replace the n final values of the array with + + Examples + -------- + >>> lead(pd.Series([1,2,3]), n=1) + 0 2.0 + 1 3.0 + 2 NaN + dtype: float64 + + >>> lead(pd.Series([1,2,3]), n=1, default = 99) + 0 2 + 1 3 + 2 99 + dtype: int64 """ res = x.shift(-1*n, fill_value = default) @@ -342,23 +357,28 @@ def _lead_grouped(x, n = 1, default = None): def lag(x, n = 1, default = None): """Return an array with each value replaced by the previous (or further backward) value in the array. - Arguments: - x: a pandas Series object - n: number of next values backward to replace each value with - default: what to replace the n final values of the array with - - Example: - >>> lag(pd.Series([1,2,3]), n=1) - 0 NaN - 1 1.0 - 2 2.0 - dtype: float64 - - >>> lag(pd.Series([1,2,3]), n=1, default = 99) - 0 99.0 - 1 1.0 - 2 2.0 - dtype: float64 + Parameters + ---------- + x: + a pandas Series object + n: + number of next values backward to replace each value with + default: + what to replace the n final values of the array with + + Examples + -------- + >>> lag(pd.Series([1,2,3]), n=1) + 0 NaN + 1 1.0 + 2 2.0 + dtype: float64 + + >>> lag(pd.Series([1,2,3]), n=1, default = 99) + 0 99.0 + 1 1.0 + 2 2.0 + dtype: float64 """ @@ -382,14 +402,16 @@ def _lag_grouped(x, n = 1, default = None): def n(x): """Return the total number of elements in the array (or rows in a DataFrame). - Example: - >>> ser = pd.Series([1,2,3]) - >>> n(ser) - 3 + Examples + -------- + + >>> ser = pd.Series([1,2,3]) + >>> n(ser) + 3 - >>> df = pd.DataFrame({'x': ser}) - >>> n(df) - 3 + >>> df = pd.DataFrame({'x': ser}) + >>> n(df) + 3 """ if isinstance(x, pd.DataFrame): @@ -405,14 +427,16 @@ def _n_grouped(x: GroupBy) -> GroupByAgg: # n_distinct ------------------------------------------------------------------ -@alias_series_agg('nunique') +@_alias_series_agg('nunique') @symbolic_dispatch(cls = Series) def n_distinct(x): """Return the total number of distinct (i.e. unique) elements in an array. - Example: - >>> n_distinct(pd.Series([1,1,2,2])) - 2 + Examples + -------- + + >>> n_distinct(pd.Series([1,1,2,2])) + 2 """ return x.nunique() @@ -424,12 +448,14 @@ def n_distinct(x): def na_if(x, y): """Return a array like x, but with values in y replaced by NAs. - Examples: - >>> na_if(pd.Series([1,2,3]), [1,3]) - 0 NaN - 1 2.0 - 2 NaN - dtype: float64 + Examples + -------- + + >>> na_if(pd.Series([1,2,3]), [1,3]) + 0 NaN + 1 2.0 + 2 NaN + dtype: float64 """ y = [y] if not np.ndim(y) else y @@ -452,31 +478,40 @@ def near(x): @symbolic_dispatch(cls = Series) def nth(x, n, order_by = None, default = None): - """Return the nth entry of x. Similar to x[n]. + """Return the nth entry of x. Similar to `x[n]`. - Note: - first(x) and last(x) are nth(x, 0) and nth(x, -1). + Parameters + ---------- + x: + series to get entry from. + n: + position of entry to get from x (0 indicates first entry). + order_by: + optional Series used to reorder x. + default: + (not implemented) value to return if no entry at n. - Arguments: - x: series to get entry from. - n: position of entry to get from x (0 indicates first entry). - order_by: optional Series used to reorder x. - default: (not implemented) value to return if no entry at n. + Notes + ----- + first(x) and last(x) are nth(x, 0) and nth(x, -1). - Examples: - >>> ser = pd.Series(['a', 'b', 'c']) - >>> nth(ser, 1) - 'b' - >>> sorter = pd.Series([1, 2, 0]) - >>> nth(ser, 1, order_by = sorter) - 'a' + Examples + -------- + + >>> ser = pd.Series(['a', 'b', 'c']) + >>> nth(ser, 1) + 'b' + + >>> sorter = pd.Series([1, 2, 0]) + >>> nth(ser, 1, order_by = sorter) + 'a' - >>> nth(ser, 0), nth(ser, -1) - ('a', 'c') + >>> nth(ser, 0), nth(ser, -1) + ('a', 'c') - >>> first(ser), last(ser) - ('a', 'c') + >>> first(ser), last(ser) + ('a', 'c') """ diff --git a/siuba/dply/verbs.py b/siuba/dply/verbs.py index bf255d92..03a91ac1 100644 --- a/siuba/dply/verbs.py +++ b/siuba/dply/verbs.py @@ -1812,10 +1812,27 @@ def anti_join(left, right = None, on = None, *args, by = None): range_indx = pd.RangeIndex(len(left)) return left.iloc[range_indx.difference(l_indx),:] + +# Define specific joins using partials ---- +# note that we include a docstring under the attribute, in order to generate +# a summary of the partials in our API reference docs. + left_join = partial(join, how = "left") +"""Join two tables, and always keep the rows of the left hand table.""" + right_join = partial(join, how = "right") +"""Join two tables, and always keep the rows of the right hand table.""" + full_join = partial(join, how = "full") +"""Join two tables, and always keep the rows of both tables.""" + inner_join = partial(join, how = "inner") +"""Join two tables, dropping rows with no matches between tables.""" + +left_join.__doc__ = join.__doc__ +right_join.__doc__ = join.__doc__ +full_join.__doc__ = join.__doc__ +inner_join.__doc__ = join.__doc__ # Head ======================================================================== From 3b3e864fd64a9c4c35a31a69285c78456f5913e3 Mon Sep 17 00:00:00 2001 From: Michael Chow Date: Mon, 27 Mar 2023 14:27:37 -0400 Subject: [PATCH 4/4] chore: re-add Pipeable import to top-level module --- siuba/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/siuba/__init__.py b/siuba/__init__.py index 7661b4f0..700029cb 100644 --- a/siuba/__init__.py +++ b/siuba/__init__.py @@ -2,7 +2,7 @@ __version__ = "0.4.2" # default imports-------------------------------------------------------------- -from .siu import _, Fx, Lam, pipe +from .siu import _, Fx, Lam, pipe, Pipeable from .dply.across import across from .dply.verbs import ( # Dply ---- @@ -30,7 +30,7 @@ __all__ = [ '_', "Fx", - "pipe", + "pipe", "Pipeable", "across", "group_by", "ungroup", "select", "rename",