Skip to content

Commit e460550

Browse files
committed
DOC: Add examples for creating an index accessor
1 parent dd56ad8 commit e460550

File tree

1 file changed

+115
-46
lines changed

1 file changed

+115
-46
lines changed

pandas/core/accessor.py

+115-46
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
)
1414
import warnings
1515

16-
from pandas.util._decorators import doc
16+
from pandas.util._decorators import doc, Appender
1717
from pandas.util._exceptions import find_stack_level
1818

1919

@@ -255,51 +255,15 @@ def _register_accessor(name: str, cls):
255255
256256
Notes
257257
-----
258-
When accessed, your accessor will be initialized with the pandas object
259-
the user is interacting with. So the signature must be
260-
261-
.. code-block:: python
262-
263-
def __init__(self, pandas_object): # noqa: E999
264-
...
265-
266-
For consistency with pandas methods, you should raise an ``AttributeError``
267-
if the data passed to your accessor has an incorrect dtype.
268-
269-
>>> pd.Series(["a", "b"]).dt
270-
Traceback (most recent call last):
271-
...
272-
AttributeError: Can only use .dt accessor with datetimelike values
273-
274-
Examples
275-
--------
276-
In your library code::
277-
278-
@pd.api.extensions.register_dataframe_accessor("geo")
279-
class GeoAccessor:
280-
def __init__(self, pandas_obj):
281-
self._obj = pandas_obj
282-
283-
@property
284-
def center(self):
285-
# return the geographic center point of this DataFrame
286-
lat = self._obj.latitude
287-
lon = self._obj.longitude
288-
return (float(lon.mean()), float(lat.mean()))
289-
290-
def plot(self):
291-
# plot this array's data on a map, e.g., using Cartopy
292-
pass
293-
294-
Back in an interactive IPython session:
295-
296-
.. code-block:: ipython
297-
298-
In [1]: ds = pd.DataFrame({{"longitude": np.linspace(0, 10),
299-
...: "latitude": np.linspace(0, 20)}})
300-
In [2]: ds.geo.center
301-
Out[2]: (5.0, 10.0)
302-
In [3]: ds.geo.plot() # plots data on a map
258+
This function allows you to register a custom-defined accessor class for {klass}.
259+
The requirements for the accessor class are as follows:
260+
261+
* Must contain an init method that:
262+
* accepts a single {klass} object
263+
* raises an {AttributeError} if the {klass} object does not have correctly matching inputs for the accessor
264+
* Must contain a method for each access pattern.
265+
* The methods should be able to take any argument signature.
266+
* Accessible using the @property decorator if no additional arguments are needed.
303267
"""
304268

305269
def decorator(accessor):
@@ -318,20 +282,125 @@ def decorator(accessor):
318282
return decorator
319283

320284

285+
_register_df_doc = """
286+
Examples
287+
--------
288+
In your library code, an accessor that only accepts integers could have a class defined like this:
289+
.. code-block:: python
290+
291+
import pandas as pd
292+
293+
@pd.api.extensions.register_dataframe_accessor("int_accessor")
294+
class IntAccessor:
295+
def __init__(self, pandas_obj):
296+
if not all(pandas_obj[col].dtype == 'int64' for col in pandas_obj.columns):
297+
raise AttributeError("All columns must contain integer values only")
298+
self._obj = pandas_obj
299+
300+
def sum(self):
301+
return self._obj.sum()
302+
303+
304+
Back in an interactive IPython session:
305+
.. code-block:: ipython
306+
>>> df = pd.DataFrame([[1, 2], ['x', 'y']])
307+
>>> df.int_accessor
308+
Traceback (most recent call last):
309+
...
310+
AttributeError: All columns must contain integer values only. Did you mean: '_accessors'?
311+
>>> df = pd.DataFrame([[1, 2], [3, 4]])
312+
>>> df.int_accessor.sum()
313+
0 4
314+
1 6
315+
dtype: int64
316+
"""
317+
318+
319+
@Appender(_register_df_doc)
321320
@doc(_register_accessor, klass="DataFrame")
322321
def register_dataframe_accessor(name: str):
323322
from pandas import DataFrame
324323

325324
return _register_accessor(name, DataFrame)
326325

327326

327+
_series_doc = """
328+
Examples
329+
--------
330+
In your library code, an accessor that only accepts integers could have a class defined like this:
331+
.. code-block:: python
332+
333+
import pandas as pd
334+
335+
@pd.api.extensions.register_series_accessor("int_accessor")
336+
class IntAccessor:
337+
def __init__(self, pandas_obj):
338+
if not all(pandas_obj[col].dtype == 'int64' for col in pandas_obj.columns):
339+
raise AttributeError("All columns must contain integer values only")
340+
self._obj = pandas_obj
341+
342+
def sum(self):
343+
return self._obj.sum()
344+
345+
346+
Back in an interactive IPython session:
347+
.. code-block:: ipython
348+
>>> df = pd.Series([1, 2, 'x'])
349+
>>> df.int_accessor
350+
Traceback (most recent call last):
351+
...
352+
AttributeError: The series must contain integer data only. Did you mean: '_accessors'?
353+
>>> df = pd.Series([1, 2, 3])
354+
>>> df.int_accessor.sum
355+
6
356+
"""
357+
358+
359+
@Appender(_series_doc)
328360
@doc(_register_accessor, klass="Series")
329361
def register_series_accessor(name: str):
330362
from pandas import Series
331363

332364
return _register_accessor(name, Series)
333365

334366

367+
_index_doc = """
368+
Examples
369+
--------
370+
In your library code, an accessor that only accepts integers could have a class defined like this:
371+
.. code-block:: python
372+
373+
import pandas as pd
374+
375+
@pd.api.extensions.register_index_accessor("int_accessor")
376+
class IntAccessor:
377+
def __init__(self, pandas_obj):
378+
if not all(isinstance(x, int) for x in pandas_obj):
379+
raise AttributeError("The index must only be an integer value")
380+
self._obj = pandas_obj
381+
382+
def even(self):
383+
return [x for x in self._obj if x % 2 == 0]
384+
385+
Back in an interactive IPython session:
386+
.. code-block:: ipython
387+
>>> df = pd.DataFrame.from_dict({
388+
'row1': {'1':1, '2':'a'},
389+
'row2': {'1':2, '2':'b'}
390+
},orient='index')
391+
>>> df.index.int_accessor
392+
Traceback (most recent call last):
393+
...
394+
AttributeError: The index must only be an integer value. Did you mean: '_accessors'?
395+
>>> df = pd.DataFrame({
396+
'col1': [1, 2, 3, 4],
397+
'col2': ['a', 'b', 'c', 'd']
398+
}, index=[1, 2, 5, 8])
399+
>>> df.index.my_accessor.even()
400+
[2,8]
401+
"""
402+
403+
@Appender(_index_doc)
335404
@doc(_register_accessor, klass="Index")
336405
def register_index_accessor(name: str):
337406
from pandas import Index

0 commit comments

Comments
 (0)