Skip to content

Commit 327340b

Browse files
committed
Merge pull request #9322 from shoyer/better-delegate-api-docs
ENH/DOC: reimplement Series delegates/accessors using descriptors
2 parents a557cef + b7a6d1b commit 327340b

15 files changed

+295
-139
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{{ fullname }}
2+
{{ underline }}
3+
4+
.. currentmodule:: {{ module.split('.')[0] }}
5+
6+
.. autoaccessorattribute:: {{ [module.split('.')[1], objname]|join('.') }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{{ fullname }}
2+
{{ underline }}
3+
4+
.. currentmodule:: {{ module.split('.')[0] }}
5+
6+
.. autoaccessormethod:: {{ [module.split('.')[1], objname]|join('.') }}

doc/source/api.rst

+69-71
Original file line numberDiff line numberDiff line change
@@ -449,114 +449,113 @@ Datetimelike Properties
449449

450450
``Series.dt`` can be used to access the values of the series as
451451
datetimelike and return several properties.
452-
Due to implementation details the methods show up here as methods of the
453-
``DatetimeProperties/PeriodProperties/TimedeltaProperties`` classes. These can be accessed like ``Series.dt.<property>``.
454-
455-
.. currentmodule:: pandas.tseries.common
452+
These can be accessed like ``Series.dt.<property>``.
456453

457454
**Datetime Properties**
458455

459456
.. autosummary::
460457
:toctree: generated/
461-
462-
DatetimeProperties.date
463-
DatetimeProperties.time
464-
DatetimeProperties.year
465-
DatetimeProperties.month
466-
DatetimeProperties.day
467-
DatetimeProperties.hour
468-
DatetimeProperties.minute
469-
DatetimeProperties.second
470-
DatetimeProperties.microsecond
471-
DatetimeProperties.nanosecond
472-
DatetimeProperties.second
473-
DatetimeProperties.weekofyear
474-
DatetimeProperties.dayofweek
475-
DatetimeProperties.weekday
476-
DatetimeProperties.dayofyear
477-
DatetimeProperties.quarter
478-
DatetimeProperties.is_month_start
479-
DatetimeProperties.is_month_end
480-
DatetimeProperties.is_quarter_start
481-
DatetimeProperties.is_quarter_end
482-
DatetimeProperties.is_year_start
483-
DatetimeProperties.is_year_end
458+
:template: autosummary/accessor_attribute.rst
459+
460+
Series.dt.date
461+
Series.dt.time
462+
Series.dt.year
463+
Series.dt.month
464+
Series.dt.day
465+
Series.dt.hour
466+
Series.dt.minute
467+
Series.dt.second
468+
Series.dt.microsecond
469+
Series.dt.nanosecond
470+
Series.dt.second
471+
Series.dt.weekofyear
472+
Series.dt.dayofweek
473+
Series.dt.weekday
474+
Series.dt.dayofyear
475+
Series.dt.quarter
476+
Series.dt.is_month_start
477+
Series.dt.is_month_end
478+
Series.dt.is_quarter_start
479+
Series.dt.is_quarter_end
480+
Series.dt.is_year_start
481+
Series.dt.is_year_end
484482

485483
**Datetime Methods**
486484

487485
.. autosummary::
488486
:toctree: generated/
487+
:template: autosummary/accessor_method.rst
489488

490-
DatetimeProperties.to_period
491-
DatetimeProperties.to_pydatetime
492-
DatetimeProperties.tz_localize
493-
DatetimeProperties.tz_convert
489+
Series.dt.to_period
490+
Series.dt.to_pydatetime
491+
Series.dt.tz_localize
492+
Series.dt.tz_convert
494493

495494
**Timedelta Properties**
496495

497496
.. autosummary::
498497
:toctree: generated/
498+
:template: autosummary/accessor_attribute.rst
499499

500-
TimedeltaProperties.days
501-
TimedeltaProperties.seconds
502-
TimedeltaProperties.microseconds
503-
TimedeltaProperties.nanoseconds
504-
TimedeltaProperties.components
500+
Series.dt.days
501+
Series.dt.seconds
502+
Series.dt.microseconds
503+
Series.dt.nanoseconds
504+
Series.dt.components
505505

506506
**Timedelta Methods**
507507

508508
.. autosummary::
509509
:toctree: generated/
510+
:template: autosummary/accessor_method.rst
510511

511-
TimedeltaProperties.to_pytimedelta
512+
Series.dt.to_pytimedelta
512513

513514
String handling
514515
~~~~~~~~~~~~~~~
515516
``Series.str`` can be used to access the values of the series as
516-
strings and apply several methods to it. Due to implementation
517-
details the methods show up here as methods of the
518-
``StringMethods`` class. These can be acccessed like ``Series.str.<function/property>``.
517+
strings and apply several methods to it. These can be acccessed like
518+
``Series.str.<function/property>``.
519519

520520
.. currentmodule:: pandas.core.strings
521521

522522
.. autosummary::
523523
:toctree: generated/
524-
525-
StringMethods.cat
526-
StringMethods.center
527-
StringMethods.contains
528-
StringMethods.count
529-
StringMethods.decode
530-
StringMethods.encode
531-
StringMethods.endswith
532-
StringMethods.extract
533-
StringMethods.findall
534-
StringMethods.get
535-
StringMethods.join
536-
StringMethods.len
537-
StringMethods.lower
538-
StringMethods.lstrip
539-
StringMethods.match
540-
StringMethods.pad
541-
StringMethods.repeat
542-
StringMethods.replace
543-
StringMethods.rstrip
544-
StringMethods.slice
545-
StringMethods.slice_replace
546-
StringMethods.split
547-
StringMethods.startswith
548-
StringMethods.strip
549-
StringMethods.title
550-
StringMethods.upper
551-
StringMethods.get_dummies
524+
:template: autosummary/accessor_method.rst
525+
526+
Series.str.cat
527+
Series.str.center
528+
Series.str.contains
529+
Series.str.count
530+
Series.str.decode
531+
Series.str.encode
532+
Series.str.endswith
533+
Series.str.extract
534+
Series.str.findall
535+
Series.str.get
536+
Series.str.join
537+
Series.str.len
538+
Series.str.lower
539+
Series.str.lstrip
540+
Series.str.match
541+
Series.str.pad
542+
Series.str.repeat
543+
Series.str.replace
544+
Series.str.rstrip
545+
Series.str.slice
546+
Series.str.slice_replace
547+
Series.str.split
548+
Series.str.startswith
549+
Series.str.strip
550+
Series.str.title
551+
Series.str.upper
552+
Series.str.get_dummies
552553

553554
.. _api.categorical:
554555

555556
Categorical
556557
~~~~~~~~~~~
557558

558-
.. currentmodule:: pandas.core.categorical
559-
560559
If the Series is of dtype ``category``, ``Series.cat`` can be used to change the the categorical
561560
data. This accessor is similar to the ``Series.dt`` or ``Series.str`` and has the
562561
following usable methods and properties (all available as ``Series.cat.<method_or_property>``).
@@ -595,7 +594,6 @@ the Categorical back to a numpy array, so levels and order information is not pr
595594

596595
Plotting
597596
~~~~~~~~
598-
.. currentmodule:: pandas
599597

600598
.. autosummary::
601599
:toctree: generated/

doc/source/conf.py

+69
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,73 @@
297297
'pd.options.display.encoding="utf8"'
298298
]
299299

300+
301+
# Add custom Documenter to handle attributes/methods of an AccessorProperty
302+
# eg pandas.Series.str and pandas.Series.dt (see GH9322)
303+
304+
from sphinx.util import rpartition
305+
from sphinx.ext.autodoc import Documenter, MethodDocumenter, AttributeDocumenter
306+
307+
308+
class AccessorLevelDocumenter(Documenter):
309+
"""
310+
Specialized Documenter subclass for objects on accessor level (methods,
311+
attributes).
312+
"""
313+
314+
# This is the simple straightforward version
315+
# modname is None, base the last elements (eg 'hour')
316+
# and path the part before (eg 'Series.dt')
317+
# def resolve_name(self, modname, parents, path, base):
318+
# modname = 'pandas'
319+
# mod_cls = path.rstrip('.')
320+
# mod_cls = mod_cls.split('.')
321+
#
322+
# return modname, mod_cls + [base]
323+
324+
def resolve_name(self, modname, parents, path, base):
325+
if modname is None:
326+
if path:
327+
mod_cls = path.rstrip('.')
328+
else:
329+
mod_cls = None
330+
# if documenting a class-level object without path,
331+
# there must be a current class, either from a parent
332+
# auto directive ...
333+
mod_cls = self.env.temp_data.get('autodoc:class')
334+
# ... or from a class directive
335+
if mod_cls is None:
336+
mod_cls = self.env.temp_data.get('py:class')
337+
# ... if still None, there's no way to know
338+
if mod_cls is None:
339+
return None, []
340+
# HACK: this is added in comparison to ClassLevelDocumenter
341+
# mod_cls still exists of class.accessor, so an extra
342+
# rpartition is needed
343+
modname, accessor = rpartition(mod_cls, '.')
344+
modname, cls = rpartition(modname, '.')
345+
parents = [cls, accessor]
346+
# if the module name is still missing, get it like above
347+
if not modname:
348+
modname = self.env.temp_data.get('autodoc:module')
349+
if not modname:
350+
modname = self.env.temp_data.get('py:module')
351+
# ... else, it stays None, which means invalid
352+
return modname, parents + [base]
353+
354+
355+
class AccessorAttributeDocumenter(AccessorLevelDocumenter, AttributeDocumenter):
356+
357+
objtype = 'accessorattribute'
358+
directivetype = 'attribute'
359+
360+
361+
class AccessorMethodDocumenter(AccessorLevelDocumenter, MethodDocumenter):
362+
363+
objtype = 'accessormethod'
364+
directivetype = 'method'
365+
366+
300367
# remove the docstring of the flags attribute (inherited from numpy ndarray)
301368
# because these give doc build errors (see GH issue 5331)
302369
def remove_flags_docstring(app, what, name, obj, options, lines):
@@ -305,3 +372,5 @@ def remove_flags_docstring(app, what, name, obj, options, lines):
305372

306373
def setup(app):
307374
app.connect("autodoc-process-docstring", remove_flags_docstring)
375+
app.add_autodocumenter(AccessorAttributeDocumenter)
376+
app.add_autodocumenter(AccessorMethodDocumenter)

doc/source/reshaping.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ This function is often used along with discretization functions like ``cut``:
478478
479479
get_dummies(cut(values, bins))
480480
481-
See also :func:`Series.str.get_dummies <pandas.core.strings.StringMethods.get_dummies>`.
481+
See also :func:`Series.str.get_dummies <pandas.Series.str.get_dummies>`.
482482

483483
.. versionadded:: 0.15.0
484484

doc/source/text.rst

+24-24
Original file line numberDiff line numberDiff line change
@@ -204,27 +204,27 @@ Method Summary
204204
:header: "Method", "Description"
205205
:widths: 20, 80
206206

207-
:meth:`~core.strings.StringMethods.cat`,Concatenate strings
208-
:meth:`~core.strings.StringMethods.split`,Split strings on delimiter
209-
:meth:`~core.strings.StringMethods.get`,Index into each element (retrieve i-th element)
210-
:meth:`~core.strings.StringMethods.join`,Join strings in each element of the Series with passed separator
211-
:meth:`~core.strings.StringMethods.contains`,Return boolean array if each string contains pattern/regex
212-
:meth:`~core.strings.StringMethods.replace`,Replace occurrences of pattern/regex with some other string
213-
:meth:`~core.strings.StringMethods.repeat`,Duplicate values (``s.str.repeat(3)`` equivalent to ``x * 3``)
214-
:meth:`~core.strings.StringMethods.pad`,"Add whitespace to left, right, or both sides of strings"
215-
:meth:`~core.strings.StringMethods.center`,Equivalent to ``pad(side='both')``
216-
:meth:`~core.strings.StringMethods.wrap`,Split long strings into lines with length less than a given width
217-
:meth:`~core.strings.StringMethods.slice`,Slice each string in the Series
218-
:meth:`~core.strings.StringMethods.slice_replace`,Replace slice in each string with passed value
219-
:meth:`~core.strings.StringMethods.count`,Count occurrences of pattern
220-
:meth:`~core.strings.StringMethods.startswith`,Equivalent to ``str.startswith(pat)`` for each element
221-
:meth:`~core.strings.StringMethods.endswith`,Equivalent to ``str.endswith(pat)`` for each element
222-
:meth:`~core.strings.StringMethods.findall`,Compute list of all occurrences of pattern/regex for each string
223-
:meth:`~core.strings.StringMethods.match`,"Call ``re.match`` on each element, returning matched groups as list"
224-
:meth:`~core.strings.StringMethods.extract`,"Call ``re.match`` on each element, as ``match`` does, but return matched groups as strings for convenience."
225-
:meth:`~core.strings.StringMethods.len`,Compute string lengths
226-
:meth:`~core.strings.StringMethods.strip`,Equivalent to ``str.strip``
227-
:meth:`~core.strings.StringMethods.rstrip`,Equivalent to ``str.rstrip``
228-
:meth:`~core.strings.StringMethods.lstrip`,Equivalent to ``str.lstrip``
229-
:meth:`~core.strings.StringMethods.lower`,Equivalent to ``str.lower``
230-
:meth:`~core.strings.StringMethods.upper`,Equivalent to ``str.upper``
207+
:meth:`~Series.str.cat`,Concatenate strings
208+
:meth:`~Series.str.split`,Split strings on delimiter
209+
:meth:`~Series.str.get`,Index into each element (retrieve i-th element)
210+
:meth:`~Series.str.join`,Join strings in each element of the Series with passed separator
211+
:meth:`~Series.str.contains`,Return boolean array if each string contains pattern/regex
212+
:meth:`~Series.str.replace`,Replace occurrences of pattern/regex with some other string
213+
:meth:`~Series.str.repeat`,Duplicate values (``s.str.repeat(3)`` equivalent to ``x * 3``)
214+
:meth:`~Series.str.pad`,"Add whitespace to left, right, or both sides of strings"
215+
:meth:`~Series.str.center`,Equivalent to ``pad(side='both')``
216+
:meth:`~Series.str.wrap`,Split long strings into lines with length less than a given width
217+
:meth:`~Series.str.slice`,Slice each string in the Series
218+
:meth:`~Series.str.slice_replace`,Replace slice in each string with passed value
219+
:meth:`~Series.str.count`,Count occurrences of pattern
220+
:meth:`~Series.str.startswith`,Equivalent to ``str.startswith(pat)`` for each element
221+
:meth:`~Series.str.endswith`,Equivalent to ``str.endswith(pat)`` for each element
222+
:meth:`~Series.str.findall`,Compute list of all occurrences of pattern/regex for each string
223+
:meth:`~Series.str.match`,"Call ``re.match`` on each element, returning matched groups as list"
224+
:meth:`~Series.str.extract`,"Call ``re.match`` on each element, as ``match`` does, but return matched groups as strings for convenience."
225+
:meth:`~Series.str.len`,Compute string lengths
226+
:meth:`~Series.str.strip`,Equivalent to ``str.strip``
227+
:meth:`~Series.str.rstrip`,Equivalent to ``str.rstrip``
228+
:meth:`~Series.str.lstrip`,Equivalent to ``str.lstrip``
229+
:meth:`~Series.str.lower`,Equivalent to ``str.lower``
230+
:meth:`~Series.str.upper`,Equivalent to ``str.upper``

doc/source/whatsnew/v0.16.0.txt

+3
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ Enhancements
107107

108108
- ``Timedelta`` will now accept nanoseconds keyword in constructor (:issue:`9273`)
109109

110+
- Added auto-complete for ``Series.str.<tab>``, ``Series.dt.<tab>`` and ``Series.cat.<tab>`` (:issue:`9322`)
111+
110112
Performance
111113
~~~~~~~~~~~
112114

@@ -198,6 +200,7 @@ Bug Fixes
198200
- Bug in groupby ``.nth()`` with a multiple column groupby (:issue:`8979`)
199201
- Bug in ``DataFrame.where`` and ``Series.where`` coerce numerics to string incorrectly (:issue:`9280`)
200202
- Bug in ``DataFrame.where`` and ``Series.where`` raise ``ValueError`` when string list-like is passed. (:issue:`9280`)
203+
- Accessing ``Series.str`` methods on with non-string values now raises ``TypeError`` instead of producing incorrect results (:issue:`9184`)
201204

202205
- Fixed division by zero error for ``Series.kurt()`` when all values are equal (:issue:`9197`)
203206

pandas/core/base.py

+22
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,28 @@ def f(self, *args, **kwargs):
166166
if not hasattr(cls, name):
167167
setattr(cls,name,f)
168168

169+
170+
class AccessorProperty(object):
171+
"""Descriptor for implementing accessor properties like Series.str
172+
"""
173+
def __init__(self, accessor_cls, construct_accessor):
174+
self.accessor_cls = accessor_cls
175+
self.construct_accessor = construct_accessor
176+
self.__doc__ = accessor_cls.__doc__
177+
178+
def __get__(self, instance, owner=None):
179+
if instance is None:
180+
# this ensures that Series.str.<method> is well defined
181+
return self.accessor_cls
182+
return self.construct_accessor(instance)
183+
184+
def __set__(self, instance, value):
185+
raise AttributeError("can't set attribute")
186+
187+
def __delete__(self, instance):
188+
raise AttributeError("can't delete attribute")
189+
190+
169191
class FrozenList(PandasObject, list):
170192

171193
"""

0 commit comments

Comments
 (0)