@@ -231,7 +231,7 @@ def __get__(self, obj, cls):
231
231
return accessor_obj
232
232
233
233
234
- @doc (klass = "" , others = "" )
234
+ @doc (klass = "" , examples = "" , others = "" )
235
235
def _register_accessor (name : str , cls ):
236
236
"""
237
237
Register a custom accessor on {klass} objects.
@@ -255,51 +255,26 @@ def _register_accessor(name: str, cls):
255
255
256
256
Notes
257
257
-----
258
- When accessed, your accessor will be initialized with the pandas object
259
- the user is interacting with. So the signature must be
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
260
261
- .. code-block:: python
261
+ * Must contain an init method that:
262
262
263
- def __init__(self, pandas_object): # noqa: E999
264
- ...
263
+ * accepts a single {klass} object
265
264
266
- For consistency with pandas methods, you should raise an ``AttributeError``
267
- if the data passed to your accessor has an incorrect dtype.
265
+ * raises an AttributeError if the {klass} object does not have correctly
266
+ matching inputs for the accessor
268
267
269
- >>> pd.Series(["a", "b"]).dt
270
- Traceback (most recent call last):
271
- ...
272
- AttributeError: Can only use .dt accessor with datetimelike values
268
+ * Must contain a method for each access pattern.
273
269
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()))
270
+ * The methods should be able to take any argument signature.
289
271
290
- def plot(self):
291
- # plot this array's data on a map, e.g., using Cartopy
292
- pass
272
+ * Accessible using the @property decorator if no additional arguments are
273
+ needed.
293
274
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
275
+ Examples
276
+ --------
277
+ {examples}
303
278
"""
304
279
305
280
def decorator (accessor ):
@@ -318,21 +293,98 @@ def decorator(accessor):
318
293
return decorator
319
294
320
295
321
- @doc (_register_accessor , klass = "DataFrame" )
296
+ _register_df_examples = """
297
+ An accessor that only accepts integers could
298
+ have a class defined like this:
299
+
300
+ >>> @pd.api.extensions.register_dataframe_accessor("int_accessor")
301
+ ... class IntAccessor:
302
+ ... def __init__(self, pandas_obj):
303
+ ... if not all(pandas_obj[col].dtype == 'int64' for col in pandas_obj.columns):
304
+ ... raise AttributeError("All columns must contain integer values only")
305
+ ... self._obj = pandas_obj
306
+ ...
307
+ ... def sum(self):
308
+ ... return self._obj.sum()
309
+ ...
310
+ >>> df = pd.DataFrame([[1, 2], ['x', 'y']])
311
+ >>> df.int_accessor
312
+ Traceback (most recent call last):
313
+ ...
314
+ AttributeError: All columns must contain integer values only.
315
+ >>> df = pd.DataFrame([[1, 2], [3, 4]])
316
+ >>> df.int_accessor.sum()
317
+ 0 4
318
+ 1 6
319
+ dtype: int64"""
320
+
321
+
322
+ @doc (_register_accessor , klass = "DataFrame" , examples = _register_df_examples )
322
323
def register_dataframe_accessor (name : str ):
323
324
from pandas import DataFrame
324
325
325
326
return _register_accessor (name , DataFrame )
326
327
327
328
328
- @doc (_register_accessor , klass = "Series" )
329
+ _register_series_examples = """
330
+ An accessor that only accepts integers could
331
+ have a class defined like this:
332
+
333
+ >>> @pd.api.extensions.register_series_accessor("int_accessor")
334
+ ... class IntAccessor:
335
+ ... def __init__(self, pandas_obj):
336
+ ... if not pandas_obj.dtype == 'int64':
337
+ ... raise AttributeError("The series must contain integer data only")
338
+ ... self._obj = pandas_obj
339
+ ...
340
+ ... def sum(self):
341
+ ... return self._obj.sum()
342
+ ...
343
+ >>> df = pd.Series([1, 2, 'x'])
344
+ >>> df.int_accessor
345
+ Traceback (most recent call last):
346
+ ...
347
+ AttributeError: The series must contain integer data only.
348
+ >>> df = pd.Series([1, 2, 3])
349
+ >>> df.int_accessor.sum()
350
+ 6"""
351
+
352
+
353
+ @doc (_register_accessor , klass = "Series" , examples = _register_series_examples )
329
354
def register_series_accessor (name : str ):
330
355
from pandas import Series
331
356
332
357
return _register_accessor (name , Series )
333
358
334
359
335
- @doc (_register_accessor , klass = "Index" )
360
+ _register_index_examples = """
361
+ An accessor that only accepts integers could
362
+ have a class defined like this:
363
+
364
+ >>> @pd.api.extensions.register_index_accessor("int_accessor")
365
+ ... class IntAccessor:
366
+ ... def __init__(self, pandas_obj):
367
+ ... if not all(isinstance(x, int) for x in pandas_obj):
368
+ ... raise AttributeError("The index must only be an integer value")
369
+ ... self._obj = pandas_obj
370
+ ...
371
+ ... def even(self):
372
+ ... return [x for x in self._obj if x % 2 == 0]
373
+ >>> df = pd.DataFrame.from_dict(
374
+ ... {"row1": {"1": 1, "2": "a"}, "row2": {"1": 2, "2": "b"}}, orient="index"
375
+ ... )
376
+ >>> df.index.int_accessor
377
+ Traceback (most recent call last):
378
+ ...
379
+ AttributeError: The index must only be an integer value.
380
+ >>> df = pd.DataFrame(
381
+ ... {"col1": [1, 2, 3, 4], "col2": ["a", "b", "c", "d"]}, index=[1, 2, 5, 8]
382
+ ... )
383
+ >>> df.index.int_accessor.even()
384
+ [2, 8]"""
385
+
386
+
387
+ @doc (_register_accessor , klass = "Index" , examples = _register_index_examples )
336
388
def register_index_accessor (name : str ):
337
389
from pandas import Index
338
390
0 commit comments