ENH/API: Scalar values for Series.rename and NDFrame.rename_axis
State: closed

closes https://github.com/pydata/pandas/issues/9494
closes https://github.com/pydata/pandas/issues/11965

Okay refactored to overload `NDFrame.rename` and `NDFrame.rename_axis`

New rules:
- Series.rename(scalar) or Series.rename(list-like) will alter `Series.name`
- NDFrame.rename(mapping) and NDFrame.rename(function) operate as before (alter labels)
- NDFrame.rename_axis(scaler) or NDFrame.rename_axis(list-like) will change the Index.name or MutliIndex.names
- NDFrame.rename_axis(mapping) and NDFrame.rename_axis(function) operate as before.

I don't like overloading the method like this, but `rename` and `rename_axis` are the right method names. It's _mostly_ backwards compatible, aside from people doing things like `try: series.rename(x): except TypeError: ...` and I don't know what our guarantees are around things like that.


Labels:
name: Indexing
description: Related to indexing on series/frames, not to indexes themselves
name: API Design
name: MultiIndex

Comments:
I would add an example (like [this](http://pandas.pydata.org/pandas-docs/stable/indexing.html#setting-metadata))

maybe in the data structures section.

Do we need an `inplace` argument for these new methods at all? You can already just mutate the index directly if you want to do that.

OK, removing inplace.

Thoughts on `pd.Series.rename(scalar)` changing the Series name, and `NDFrame.rename_axis(scalar/list-like)` changing the axis name(s)? Those methods will keep their behavior for mappings / function inputs IIUC this is what `xray` does?

that would be quite an API change

I think it'd be backwards compatible right? Right now we raise a TypeError on scalars / array-likes. I'll writeup an example later.

FYI, changed the API around, see the updated [original post](https://github.com/pydata/pandas/pull/11980#issue-125322740), which makes it more like [xray](http://xray.readthedocs.org/en/stable/generated/xray.DataArray.rename.html?highlight=rename#xray.DataArray.rename)

Basically we look at the type of the argument. For dict/functions we alter labels as before. For scalar/list-like we alter Series/index name(s).

**Question**: what would be the _ideal name for the original functionality_ (i.e. changing the column names/index labels) if we could choose it know? Would that be something else as `rename`?

If we think of something really better, we could also opt to add that as a method, and at least use two different functions in the documentation for these two different operation that we can recommend to use (kind of soft deprecation)

Further, one drawback of the overloading of rename, is that you cannot rename a certain level of a MultiIndex using a mapping like `{'original_level_name': 'new_level_name'}` (instead of having to specify the level you want to rename with `level=..`)

> what would be the ideal name for the original functionality

`relabel` I think.

And you're correct not being able to rename a single level of a MultiIndex; you'd have to specify all the levels, even the ones you aren't changing.. We could potentially add a `level` argument `rename`, but I think that's more confusion than it's worth and I didn't want to change the signature at all.

note #4160 for long time bug in renaming `MultiIndex`

Have `Series.rename` do different things if its a scalar, versus a mapping is way too magical.

These need to be separate functions.

I agree that it's not ideal. `rename` is the correct name if we started from scratch today though. Are there any alternate names that aren't much worse?  I don't think we should use my original implementation of `set_name` since `set_axis` (and I think most of our other `set_*` functions operate inplace and return None.

We also have Joris' suggestion of doing creating a `.relabel` that is identical to the current `rename` and encouraging people to use that for altering the axis labels (but not deprecating or warning)

whats wrong with `set_name`?

it's very clear

@TomAugspurger looking thru this, its seems that `.rename` and `.rename_axis` will work for the purpose of chaining a Series name and axis name (respectively). Why do we also need `.set_name` and `.set_axis_name` ?

We only need one or the other (`.rename/rename_axis` or `.set_name`/`.set_axis_name`). My most recent commit removed `.set_name` and `.set_axis_name`, but I didn't update the title. The choice is between
1. `.rename`, which I think is the better name but overloads the `.rename` / `.rename_axis` methods to also handle relabeling
2. `.set_name`. Before I said that most of our `set_*` methods worked inplace and returned None. Looking more, `Index.set_names` doesn't operate in place and does return the new Index with new names. That removes my biggest objection to `set_name`.

@jreback I think your preference is for number 2?

@TomAugspurger ahh i c now.

I actually like 1). Seems pretty straightforward (I think I was misreading this a bit before).

I would also deprecate `Index.set_names` and change it to `Index.rename` for consistency.

I'll update tonight. Just to double check, we're all OK moving forward with my current implementation of overloading `rename` and `rename_axis`?

yep

My last push swallowed the conversation on checking for `dict-likes`. For now I've put a check in `common` `com.is_dict_like` like identical to @shoyer's (checking for `__getitem__` and `.keys`). Happy to change that / superseed it once we figure out https://github.com/pydata/pandas/issues/12056

@jreback could you expand a bit on what you mean by dictviews? I don't think that `dict.keys()` / `dict.values()` / `dict.itms()`

And on the docstrings, I don't think there's a way to only show certain examples for Series and others for DataFrames, is there? We have other docstrings showing both. I did have add an example with `DataFrame.rename(scalar)` raising a TypeError.

Other than those I think this is ready. The 2.7 build failed while building the docs because the xarray docstring you fixed in https://github.com/pydata/pandas/commit/4404d396442449d6033b1ab74818940ee468ee36

@TomAugspurger nvm, was thinking of handling the actual `keys` which are `dict_keys` IOW, views. but that is not what you are doing.

Ok, thanks!

side issue: we might want to revisit the signature of `.rename` & `.reindex` to be more like

`.rename(values, axis)` more in-line with other methods I think.

e.g.

`.rename(lambda x: str(x), axis=1)` rather than `.rename(columns=lambda x: str(x))`
`.reindex(values, axis=1)` rather than `.reindex(columns=values)`

Think that's worth breaking API for?

With `rename` we at least have `rename_axis`. Could point users to that if you want.

At the very least, it's nice that these two methods are at least similar to eachother. They're both for altering the axis labels, and they both take `index=, columns-`

actually we also have `.reindex_axis` so I guess `.rename/.reindex` are the `axis-less` ones....