ENH Series.getattr for attributes lacking built-in accessors
State: closed

Adds a `Series.getattr()` method for accessing attributes from a Series of objects that do not have built-in accessors like those in `Series.str` or `Series.dt`. This is faster than mapping/applying a `lambda obj: obj.attr` function because it uses `operator.attrgetter` and is also syntactically concise, similar to other accessors like `series.imag` or `series.dt.date`.

- [x] tests added / passed
- [X] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [X] whatsnew entry


Labels:
name: API Design

Comments:
Hello @pinakibose! Thanks for updating the PR.





Cheers ! There are no PEP8 issues in this Pull Request. :beers: 

##### Comment last updated on January 16, 2019 at 19:11 Hours UTC
can u show a real use case with and w/o this function
Here is an example I added to the test suite:
```
Point = collections.namedtuple('Point', ['x', 'y'])
ptser = pd.Series([Point(1, 2), Point(3, 7)])
```

With this method, we can write:
```
ptser.getattr('x')
```

Without this method, we would have to write:
```
ptser.map(operator.attrgetter('x'))
```

...and even then it wouldn't work if `ptser` has `None` or `np.nan` in it:
```
ptser = pd.Series([Point(1, 2), Point(3, 7), None])
ptser.map(operator.attrgetter('x'))  # won't work
ptser.getattr('x')  # NaN where missing
```

A `Point` class is admittedly a little contrived, but this method would be a lot more useful for more complex objects with attributes.
I think the most compelling use cases are already covered by the existing
accessors.

In the case of the Point example, I'd argue that `None` isn't a good
missing value indicator for that type. It'd be better to use Point(nan,
nan).

Personally, I've OK with users writing `.map(lambda x: getattr(x, 'x',
None))`, as it's clear what happens if the getattr fails. It's not clear
what
the default value would be for a generic Series.getattr.

On Wed, Jan 16, 2019 at 1:36 PM pinakibose <notifications@github.com> wrote:

> Here is an example I added to the test suite:
>
> Point = collections.namedtuple('Point', ['x', 'y'])
> ptser = pd.Series([Point(1, 2), Point(3, 7)])
>
> With this method, we can write:
>
> ptser.getattr('x')
>
> Without this method, we would have to write:
>
> ptser.map(operator.attrgetter('x'))
>
> ...and even then it wouldn't work if ptser has None or np.nan in it:
>
> ptser = pd.Series([Point(1, 2), Point(3, 7), None])
> ptser.map(operator.attrgetter('x'))  # won't work
> ptser.getattr('x')  # NaN where missing
>
> A Point class is admittedly a little contrived, but this method would be
> a lot more useful for more complex objects with attributes.
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/pandas-dev/pandas/pull/24808#issuecomment-454910782>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ABQHIo4xESo7_w_xl1LtBHQw6tCB-b2pks5vD38tgaJpZM4aDvO2>
> .
>

Would it make more sense if a `default=nan` parameter were included?

I mainly see this from a readability perspective. I personally often end up with DataFrames full of objects from other modules, and I think `foo_series.getattr('bar.baz')` is a little more readable and robust to missing values than `foo_series.map(lambda x: x.bar.baz)`, especially when when I'm accustomed to seeing `foo.bar.baz` from working with a single objects.

To make things look nicer, I've sometimes monkey-patched `pd.Series` like this:
```
pd.Series.getattr = lambda S, x: S.apply(operator.attrgetter(x))
```

I'm wondering if anyone else would benefit from this piece of syntactic sugar when working with `Series`s full of non-Pandas objects.
I'm sure some people would benefit from and appreciate it, but we need to
balance that against the cost of every new method. Each method makes pandas
harder to learn and remember how to use.

On Wed, Jan 16, 2019 at 2:08 PM pinakibose <notifications@github.com> wrote:

> Would it make more sense if a default=nan parameter were included?
>
> I mainly see this from a readability perspective. I personally often end
> up with DataFrames full of objects from other modules, and I think
> foo_series.getattr('bar.baz') is a little more readable than foo_series.map(lambda
> x: x.bar.baz), especially when when I'm accustomed to seeing foo.bar.baz
> from working with a single objects.
>
> To make things look nicer, I've sometimes monkey-patched pd.Series like
> this:
>
> pd.Series.getattr = lambda S, x: S.apply(operator.attrgetter(x))
>
> I'm wondering if anyone else would benefit from this piece of syntactic
> sugar when working with Seriess full of non-Pandas objects.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/pandas-dev/pandas/pull/24808#issuecomment-454921650>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/ABQHIjfifmElsptFw2mfXt8Szd1_l6uMks5vD4a6gaJpZM4aDvO2>
> .
>

Understood. Thank you for considering.
Note that `map` also has a keyword to deal with missing values, in case your function cannot:

```
In [34]: ptser = pd.Series([Point(1, 2), Point(3, 7), None])                    

In [35]: ptser.map(operator.attrgetter('x'), na_action='ignore')                
Out[35]: 
0    1.0
1    3.0
2    NaN
dtype: float64
```
closing as would bloat the API