[ 
https://issues.apache.org/jira/browse/BEAM-12169?focusedWorklogId=719860&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-719860
 ]

ASF GitHub Bot logged work on BEAM-12169:
-----------------------------------------

                Author: ASF GitHub Bot
            Created on: 03/Feb/22 03:27
            Start Date: 03/Feb/22 03:27
    Worklog Time Spent: 10m 
      Work Description: TheNeuralBit commented on a change in pull request 
#16615:
URL: https://github.com/apache/beam/pull/16615#discussion_r798182273



##########
File path: sdks/python/apache_beam/dataframe/frames.py
##########
@@ -4625,9 +4625,48 @@ def repeat(self, repeats):
       raise TypeError("str.repeat(repeats=) value must be an int or a "
                       f"DeferredSeries (encountered {type(repeats)}).")
 
-  get_dummies = frame_base.wont_implement_method(
-      pd.core.strings.StringMethods, 'get_dummies',
-      reason='non-deferred-columns')
+  @frame_base.with_docs_from(pd.core.strings.StringMethods)
+  @frame_base.args_to_kwargs(pd.core.strings.StringMethods)
+  def get_dummies(self, **kwargs):
+    """
+    Series must be categorical type. Either cast to ``category`` to
+    infer categories, or preferred, cast to ``CategoricalDtype``
+    to ensure correct categories.

Review comment:
       A couple of points here:
   - pandas documentation seems to prefer "dtype" rather than "type".
   - casting to "category" to infer categories won't work for us, because we 
need to know the specific categories at construction time in order to figure 
out the proxy. But at construction time we don't have access to the data to 
infer the types from. So really the only option is if the users specifies the 
categories with `CategoricalDtype`. (Also, once we have a few operations that 
work this way, we should add some general documentation about how to do this at 
https://s.apache.org/dataframe-non-deferred-columns, which gets linked in error 
messages, and then we could link it here too)

##########
File path: sdks/python/apache_beam/dataframe/pandas_doctests_test.py
##########
@@ -554,6 +554,8 @@ def test_string_tests(self):
                 's.str.repeat(repeats=[1, 2, 3])'
             ],
             f'{module_name}.str_repeat': ['s.str.repeat(repeats=[1, 2, 3])'],
+            # get_dummies examples are not casted to Categorical type
+            # Must be Categorical or Bool to work in Beam
             f'{module_name}.StringMethods.get_dummies': ['*'],
             f'{module_name}.str_get_dummies': ['*'],

Review comment:
       I think it makes sense to keep the wildcard, I don't think pandas will 
ever have doctests for the specific case (CategoricalDtype) that we support.

##########
File path: sdks/python/apache_beam/dataframe/transforms_test.py
##########
@@ -334,6 +358,49 @@ def test_repeat(self):
     })
     self.run_scenario(df, lambda df: df.strings.str.repeat(df.repeats))
 
+  def test_get_dummies(self):
+    # Should not work because series is not a categorical type
+    with self.assertRaisesRegex(
+        frame_base.WontImplementError,
+        r"get_dummies\(\) of non-categorical type is not supported"):
+      s = pd.Series(['a ,b', 'a', 'a, d'])
+      self.run_scenario(s, lambda s: s.str.get_dummies(','), check_subset=True)
+
+    # Different separator
+    s = pd.Series(['a ,b', 'a', 'a, d', 'c'])
+    s = s.astype(pd.CategoricalDtype(categories=['a ,b', 'c', 'b', 'a,d']))
+    self.run_scenario(s, lambda s: s.str.get_dummies(','), check_subset=True)
+
+    # Pandas docs example 1
+    s = pd.Series(['a|b', 'a', 'a|c']).astype('category')
+    self.run_scenario(s, lambda s: s.str.get_dummies(','), check_subset=True)
+
+    # Pandas docs example 2
+    # Shouldn't still work even though np.nan is not considered a category
+    # because we automatically create a nan column
+    s = pd.Series(['a|b', np.nan, 'a|c']).astype('category')
+    self.run_scenario(s, lambda s: s.str.get_dummies(), check_subset=True)
+
+    # Should have two columns c, nan
+    s = pd.Series(['a|b', 'b|c', 'a|c', 'c', 'd'])
+    s = s.astype(pd.CategoricalDtype(categories=['a', 'b', 'c']))
+    self.run_scenario(s, lambda s: s.str.get_dummies(), check_subset=True)
+
+    # Explicitly pass nan as a category
+    s = pd.Series(['a|b', 'b|c', 'a|c', 'c', 'd'])
+    s = s.astype(pd.CategoricalDtype(categories=['a', 'b', 'c', 'nan']))
+    self.run_scenario(s, lambda s: s.str.get_dummies(), check_subset=True)
+
+    # Bools do not work because they are not a string type
+    with self.assertRaisesRegex(
+        AttributeError, r"Can only use .str accessor with string values"):
+      s = pd.Series([True, False, False, True])
+      self.run_scenario(s, lambda s: s.str.get_dummies(), check_subset=True)
+
+    # Bools casted to string work
+    s = pd.Series([True, False, False, True]).astype('str').astype('category')
+    self.run_scenario(s, lambda s: s.str.get_dummies(), check_subset=True)

Review comment:
       I think it would be preferable to test this in `frames_test.py`, since 
that's where we verify most operations. If it doesn't make sense to use 
`run_test` like we do for most of the tests there you could test this in 
`BeamSpecificTests`.  Also since you already have these different test cases 
separated out nicely, you might as well make them separate test cases: 
`test_get_dummies_pandas_doc_example_1`, 
`test_get_dummies_bools_casted_to_string`, etc..

##########
File path: sdks/python/apache_beam/dataframe/frames.py
##########
@@ -4625,9 +4625,48 @@ def repeat(self, repeats):
       raise TypeError("str.repeat(repeats=) value must be an int or a "
                       f"DeferredSeries (encountered {type(repeats)}).")
 
-  get_dummies = frame_base.wont_implement_method(
-      pd.core.strings.StringMethods, 'get_dummies',
-      reason='non-deferred-columns')
+  @frame_base.with_docs_from(pd.core.strings.StringMethods)
+  @frame_base.args_to_kwargs(pd.core.strings.StringMethods)
+  def get_dummies(self, **kwargs):
+    """
+    Series must be categorical type. Either cast to ``category`` to
+    infer categories, or preferred, cast to ``CategoricalDtype``
+    to ensure correct categories.
+
+    Booleans are not supported because entries must be a string type.
+    First cast to ``string`` and then to ``category`` if data contains
+    booleans.

Review comment:
       I think it's ok not to mention booleans here, since it's in `Series.str` 
it should be clear that we won't support booleans.

##########
File path: sdks/python/apache_beam/dataframe/transforms_test.py
##########
@@ -73,29 +74,52 @@ def df_equal_to(expected):
 
 
 class TransformTest(unittest.TestCase):
-  def run_scenario(self, input, func):
+  def run_scenario(self, input, func, check_subset=False):
+    """
+    In order for us to perform non-deferred operations, we have
+    to enumerate all possible categories, even if they are
+    unobserved. The default Pandas implementation on the other hand
+    does not produce unobserved columns. This means when conducting
+    tests, we need to account for the fact that the Beam result may
+    be a superset of that of the Pandas result.
+
+    When ``check_subset`` is set to ``True``, we will only
+    check that all of the columns in the Pandas implementation is
+    contained in that of the Beam implementation.

Review comment:
       It might be a bit more robust if we also verify that any columns that 
don't exist in `expected` are all NaN in `actual`




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Issue Time Tracking
-------------------

    Worklog Id:     (was: 719860)
    Time Spent: 1h 50m  (was: 1h 40m)

> Allow non-deferred column operations on categorical columns
> -----------------------------------------------------------
>
>                 Key: BEAM-12169
>                 URL: https://issues.apache.org/jira/browse/BEAM-12169
>             Project: Beam
>          Issue Type: Improvement
>          Components: dsl-dataframe, sdk-py-core
>            Reporter: Brian Hulette
>            Assignee: Andy Ye
>            Priority: P3
>              Labels: dataframe-api
>          Time Spent: 1h 50m
>  Remaining Estimate: 0h
>
> There are several operations that we currently disallow because they produce 
> a variable set of columns in the output based on the data 
> (non-deferred-columns). However, for some dtypes (categorical, boolean) we 
> can easily enumerate all the possible values that will be seen at execution 
> time, so we can predict the columns that will be seen.
> Note we still can't implement these operations 100% correctly, as pandas will 
> typically only create columns for the values that are _observed_, while we'd 
> have to create a column for every possible value.
> We should allow these operations in these special cases.
> Operations in this category:
>  - DataFrame.unstack (can work if unstacked level is a categorical or boolean 
> column)
>  - Series.str.get_dummies
>  - Series.str.split
>  - Series.str.rsplit
>  - DataFrame.pivot
>  - DataFrame.pivot_table
>  - len(GroupBy) and ngroups
>  ** if groupers are all categorical _and_ observed=False or all boolean
>  ** Note these two may not actually be equivalent in all cases: 
> [https://github.com/pandas-dev/pandas/issues/26326]



--
This message was sent by Atlassian Jira
(v8.20.1#820001)

Reply via email to