-
-
Notifications
You must be signed in to change notification settings - Fork 18.4k
WIP/DO NOT MERGE: Categorical improvements #7444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
can u show the rendered one in the top section? |
@@ -0,0 +1,721 @@ | |||
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it not useful to actually include this file; I only wanted to see the rendered version in the top section of the PR, which could simply be a ipython session and/or jpg of the rendered html
I am not a big fan of the long Series accessors, e.g. however, easy enough to do something like
|
EDIT: Doc removed and new version added below |
@jreback: I like the idea with |
naw will raise a TypeError |
very easy impl
|
I've updated the docs and added new unittests for slicing (lots of failures and crashes :-( ) Edit: I also changed a typo in one of my earlier commits |
I updated the main PR; all of the slicing FIXME's are done: #7217 |
Thanks! I started to fix that at the completely wrong place and got stuck right away :/ |
@jreback I've fixed some screwups in my slicing testcases and added more slicing tests. I also added assigning testcases (which of course fail because assigning is not yet implmented). I do hope that I got the testcases right... |
ok thanks |
Assign value already in levels
Assign value NOT in levels, just add the levels to the end
|
Assigning values not in levels should raise: if you want to have them included, you need to first add such a level and then assign the value. If levels and level ordering has a meaning, it makes no sense to add a level just because a value was assigned: |
ok gr8....thxs |
This is what R does: > factor(c(1,2,3,4))
[1] 1 2 3 4
Levels: 1 2 3 4
> a = factor(c(1,2,3,4))
> a[1]
[1] 1
Levels: 1 2 3 4
> a[1] <- 6
Warning message:
In `[<-.factor`(`*tmp*`, 1, value = 6) : invalid factor level, NA generated (One difference: a single value is also of type factor...) |
ok....changed to raising (easy enough for say an |
yep...that's what I do from your branch |
[removed and added in the first comment] |
I changed |
But this would mean that the default is changed to "new style", which will break old code? |
I think I changed the semantics, but were'nt you +1 on just making newstyle (compat=False) the default anyhow? |
I'm fine with it, but wasn't sure if you intended to break old code with a rename :-) If you are ok with the API change, I would remove the codepath completely and just add something similar to the |
ok....i'll leave for now, pls review after I push again (i'll let u know) |
got fillna working with a value (assume its an error if its NOT in the levels) for method='pad/bfill` its an error if its not an ordered Categorical |
As far as I understood |
updated |
@jreback fix for describe -> This fixes IMO these two entries under "FIXME":
Re level information on print: If series prints dtype, I think it would be nice to also print level information if Re "Documentation" -> There should be two more entries: BTW: where should these API change notes go? |
rebase: I added a v0.15.0.txt file: 0b6ea39 |
rebased and added a commit to print level information when printing a From my standpoint, only the groupby/pivot testcases and getting |
I am not 100% sure about printing the levels, maybe just print the number of levels? |
Release notes added. I will add truncating to the level footer I'm also thinking to change the guard in Current: # Rename the levels and simultaneously add the missing levels at the end
df["grade"].cat.levels = ["very good", "good", "very bad", "bad", "medium"]
# Reorder the levels
df["grade"].cat.reorder_levels(["very bad", "bad", "medium", "good", "very good"]) Then: # Rename the levels
df["grade"].cat.levels = ["very good", "good", "very bad"]
# Reorder the levels and simultaneously add the missing levels
df["grade"].cat.reorder_levels(["very bad", "bad", "medium", "good", "very good"]) Thoughts? |
Added the new reorder_levels feature. Now you can add levels both with assigning to |
updated |
@JanSchulz gr8! that looks better than what I was doing |
How about somethign for the last line like? (more how you do unordered), so then they are the same except for the trruncation separator?
|
I actually modeled that after the way R prints factors: > factor(c(5,2,3,4))
[1] 5 2 3 4
Levels: 2 3 4 5
> factor(c(5,2,3,4), ordered=T)
[1] 5 2 3 4
Levels: 2 < 3 < 4 < 5 If I change only the truncation separator, there is no difference in case the levels are not truncated which IMO will be mostly the case (e.g. Lickert scales have at most 7 levels + "no answer") I like the At one point I had the Series printout in the same order as the Categorical printout (currently the Categorical first prints Name,... and then levels and the Series is the other way around like for frequencies. And thoughts on this or simple leave as is? |
Hmm, try to conform to the Series as much as possible (it can be changed, but should be a deliberate action). For a future PR. I think the R printing ordered factors is just to verbose (you can just use '...' as the truncation separate for both), after all it DOES say ordered/unordered already. |
Actually, In[3]: import pandas as pd
In[4]: pd.Series(pd.Categorical([1,2,3,4,5,6]))
Out[4]:
0 1
1 2
2 3
3 4
4 5
5 6
Levels (6, ordered, int64): 1 < 2 < 3 < 4 < 5 < 6, I like "<" because it clearly shows which way the levels are ordered: reading a My suggestion would be to change truncating to [Edit: yikes, a trailing |
@JanSchulz that looks fine, and yes I would reduce |
GH3943, GH5313, GH5314, GH7444 ENH: delegate _reduction and ops from Series to the categorical to support min/max and raise TypeError on other ops (numerical) and reduction Add Categorical Properties to Series Default to 'ordered' Categoricals if values are ordered Categorical: add level assignments and reordering + changed default for ordered Add a `Categorical.reorder_levels()` method. Change some naming in `Series`, so that the methods do not clash with established standards and rename the other categorical methods accordingly. Also change the default for `ordered` to True if values + levels are passed in at creation time. Initial doc version for working with Categorical data Categorical: add Categorical.mode() and use that in Series.mode() Categorical: implement remove_unused_levels() Categorical: implement value_count() for categorical series Categorical: make Series.astype("category") work ENH: add setitem to Categorical BUG: assigning to levels not in level set now raises ValueError API: disallow numpy ufuncs with categoricals Categorical: Categorical assignment to int/obj column ENH: add support for fillna to Categoricals API: deprecate old style categorical constructor usage and change default Before it was possible to pass in precomputed labels/pointer and the corresponding levels (e.g.: `Categorical([0,1,2], levels=["a","b","c"])`). This could lead to subtle errors in case of integer categoricals: the following could be both interpreted as "precomputed pointers and levels" or "values and levels", but converting it back to a integer array would result in different arrays: `np.array(Categorical([1,2], levels=[1,2,3]))` interpreted as pointers: `[2,3]` interpreted as values: `[1,2]` Up to now we would favour old style "pointer and levels" if these values could be interpreted as such (see code for details...). With this commit we favour new style "values and levels" and only attempt to interprete them as "pointers and levels" if "compat=True" is passed to the constructor. BREAKS: This will break code which uses Categoricals with "pointer and levels". A short google search and a search on stackoverflow revealed no such useage. Categorical: document constructor changes and small fixes Categorical: document that inappropriate numpy functions won't work anymore ENH: concat support
Doc: Add Release notes for pandas-dev#7217
ERR: codes modification raises ValueError always Categorical: use Categorical.from_codes() in a few places Categorical: Fix assigning a Categorical to an existing string column
DISPLAY: show dtype when displaying Categorical series (for consistency)
…t (for select_dtypes)
closed by #7217 |
This is a PR to make discussing the doc changes easier. See #7217 for the main PR
TODO List: now in #7217
The Docs (updated 1st july, 4pm CEST)
Categorical¶
New in version 0.15.
Note
While there was in pandas.Categorical in earlier versions, the ability to use Categorical data in Series and DataFrame is new.
This is a short introduction to pandas Categorical type, including a short comparison with R’s factor.
Categoricals are a pandas data type, which correspond to categorical variables in statistics: a variable, which can take on only a limited, and usually fixed, number of possible values (commonly called levels). Examples are gender, social class, blood types, country affiliations, observation time or ratings via Likert scales.
In contrast to statistical categorical variables, a Categorical might have an order (e.g. ‘strongly agree’ vs ‘agree’ or ‘first observation’ vs. ‘second observation’), but numerical operations (additions, divisions, ...) are not possible.
All values of the Categorical are either in levels or np.nan. Order is defined by the order of the levels, not lexical order of the values. Internally, the data structure consists of a levels array and an integer array of level_codes which point to the real value in the levels array.
Categoricals are useful in the following cases:
See also the API docs on Categoricals.
Object Creation¶
Categorical Series or columns in a DataFrame can be crated in several ways:
By passing a Categorical object to a Series or assigning it to a DataFrame:
By converting an existing Series or column to a category type:
By using some special functions:
Categoricals have a specific category dtype:
Note
In contrast to R’s factor function, a Categorical is not converting input values to string and levels will end up the same data type as the original values.
Note
I contrast to R’s factor function, there is currently no way to assign/change labels at creation time. Use levels to change the levels after creation time.
To get back to the original Series or numpy array, use Series.astype(original_dtype) or np.asarray(categorical):
Working with levels¶
Categoricals have a levels property, which list their possible values. If you don’t manually specify levels, they are inferred from the passed in values. Series of type category expose the same interface via their cat property.
Note
New Categorical are automatically ordered if the passed in values are sortable or a levels argument is supplied. This is a difference to R’s factors, which are unordered unless explicitly told to be ordered (ordered=TRUE).
It’s also possible to pass in the levels in a specific order:
Note
Passing in a levels argument implies ordered=True.
Any value omitted in the levels argument will be replaced by np.nan:
Renaming levels is done by assigning new values to the Category.levels or Series.cat.levels property:
Note
I contrast to R’s factor function, a Categorical can have levels of other types than string.
Levels must be unique or a ValueError is raised:
Appending a level can be done by assigning a levels list longer than the current levels:
Removing a level is also possible, but only the last level(s) can be removed by assigning a shorter list than current levels. Values which are omitted are replaced by np.nan.
Note
It’s only possible to remove or add a level at the last position. If that’s not where you want to remove an old or add a new level, use Category.reorder_levels(new_order) or Series.cat.reorder_levels(new_order) methods before or after.
Removing unused levels can also be done:
Note
In contrast to R’s factor function, passing a Categorical as the sole input to the Categorical constructor will not remove unused levels but create a new Categorical which is equal to the passed in one!
Ordered or not...¶
If a Categoricals is ordered (cat.ordered == True), then the order of the levels has a meaning and certain operations are possible. If the categorical is unordered, a TypeError is raised.
Note
ordered=True is not necessary needed in the second case, as lists of strings are sortable and so the resulting Categorical is ordered.
Sorting will use the order defined by levels, not any lexical order present on the data type. This is even true for strings and numeric data:
Reordering the levels is possible via the Categorical.reorder_levels(new_levels) or Series.cat.reorder_levels(new_levels) methods:
Note
Note the difference between assigning new level names and reordering the levels: the first renames levels and therefore the individual values in the Series, but if the first position was sorted last, the renamed value will still be sorted last. Reordering means that the way values are sorted is different afterwards, but not that individual values in the Series are changed.
Operations¶
The following operations are possible with categorical data:
Getting the minimum and maximum, if the categorical is ordered:
Note
If the Categorical is not ordered, Categorical.min() and Categorical.max() and the corresponding operations on Series will raise TypeError.
The mode:
Note
Numeric operations like +, -, *, / and operations based on them (e.g. .median(), which would need to compute the mean between two values if the length of an array is even) do not work and raise a TypeError.
Series methods like Series.value_counts() will use all levels, even if some levels are not present in the data:
Groupby will also show “unused” levels:
Pivot tables:
Data munging¶
The optimized pandas data access methods .loc, .iloc, .ix .at, and .iat, work as normal, the only difference is the return type (for getting) and that only values already in the levels can be assigned.
Getting¶
If the slicing operation returns either a DataFrame or a a column of type Series, the category dtype is preserved.
An example where the Categorical is not preserved is if you take one single row: the resulting Series is of dtype object:
Returning a single item from a Categorical will also return the value, not a Categorical of length “1”.
Note
This is a difference to R’s factor function, where factor(c(1,2,3))[1] returns a single value factor.
To get a single value Series of type category pass in a single value list:
Setting¶
Setting values in a categorical column (or Series) works as long as the value is included in the levels:
Setting values by assigning a Categorical will also check that the levels match:
Assigning a Categorical to parts of a column of other types will use the values:
Merging¶
You can concat two DataFrames containing categorical data together, but the levels of these Categoricals need to be the same:
The same applies to df.append(df).
Getting Data In/Out¶
Writing data (Series, Frames) to a HDF store and reading it in entirety works. Querying the hdf store does not yet work.
Writing to a csv file will convert the data, effectively removing any information about the Categorical (levels and ordering). So if you read back the csv file you have to convert the relevant columns back to category and assign the right levels and level ordering.
Missing Data¶
pandas primarily uses the value np.nan to represent missing data. It is by default not included in computations. See the Missing Data section
There are two ways a np.nan can be represented in Categorical: either the value is not available or np.nan is a valid level.
Gotchas¶
Categorical is not a numpy array¶
Currently, Categorical and the corresponding category Series is implemented as a python object and not as a low level numpy array dtype. This leads to some problems.
numpy itself doesn’t know about the new dtype:
Using numpy functions on a Series of type category should not work as Categoricals are not numeric data (even in the case that .levels is numeric).
Note
If such a function works, please file a bug at https://github.com/pydata/pandas!
Side effects¶
Constructing a Series from a Categorical will not copy the input Categorical. This means that changes to the Series will in most cases change the original Categorical:
Use copy=True to prevent such a behaviour:
Note
This also happens in some cases when you supply a numpy array instea dof a Categorical: using an int array (e.g. np.array([1,2,3,4])) will exhibit the same behaviour, but using a string array (e.g. np.array(["a","b","c","a"])) will not.
Danger of confusion¶
Both Series and Categorical have a method .reorder_levels() but for different things. For Series of type category this means that there is some danger to confuse both methods.
See also the API documentation for pandas.Series.reorder_levels() and pandas.Categorical.reorder_levels()
Old style constructor usage¶
I earlier versions, a Categorical could be constructed by passing in precomputed level_codes (called then labels) instead of values with levels. The level_codes are interpreted as pointers to the levels with -1 as NaN. This usage is now deprecated and not available unless compat=True is passed to the constructor of Categorical.
In the default case (compat=False) the first argument is interpreted as values.
Warning
Using Categorical with precomputed level_codes and levels is deprecated and a FutureWarning is raised. Please change your code to use one of the proper constructor modes instead of adding compat=False.
No categorical index¶
There is currently no index of type category, so setting the index to a Categorical will convert the Categorical to a normal numpy array first and therefore remove any custom ordering of the levels:
Note
This could change if a CategoricalIndex is implemented (see #7629)
dtype in apply¶
Pandas currently does not preserve the dtype in apply functions: If you apply along rows you get a Series of object dtype (same as getting a row -> getting one element will return a basic type) and applying along columns will also convert to object.
Future compatibility¶
As Categorical is not a native numpy dtype, the implementation details of Series.cat can change if such a numpy dtype is implemented.