PageRenderTime 58ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 1ms

/pandas/core/generic.py

http://github.com/wesm/pandas
Python | 11070 lines | 11022 code | 12 blank | 36 comment | 45 complexity | 2c878bb09b63de04e2c88ceb91926ce0 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. # pylint: disable=W0231,E1101
  2. import collections
  3. from datetime import timedelta
  4. import functools
  5. import gc
  6. import json
  7. import operator
  8. from textwrap import dedent
  9. import warnings
  10. import weakref
  11. import numpy as np
  12. from pandas._libs import Timestamp, iNaT, properties
  13. import pandas.compat as compat
  14. from pandas.compat import (
  15. cPickle as pkl, isidentifier, lrange, lzip, map, set_function_name,
  16. string_types, to_str, zip)
  17. from pandas.compat.numpy import function as nv
  18. from pandas.errors import AbstractMethodError
  19. from pandas.util._decorators import (
  20. Appender, Substitution, rewrite_axis_style_signature)
  21. from pandas.util._validators import validate_bool_kwarg, validate_fillna_kwargs
  22. from pandas.core.dtypes.cast import maybe_promote, maybe_upcast_putmask
  23. from pandas.core.dtypes.common import (
  24. ensure_int64, ensure_object, is_bool, is_bool_dtype,
  25. is_datetime64_any_dtype, is_datetime64tz_dtype, is_dict_like,
  26. is_extension_array_dtype, is_integer, is_list_like, is_number,
  27. is_numeric_dtype, is_object_dtype, is_period_arraylike, is_re_compilable,
  28. is_scalar, is_timedelta64_dtype, pandas_dtype)
  29. from pandas.core.dtypes.generic import ABCDataFrame, ABCPanel, ABCSeries
  30. from pandas.core.dtypes.inference import is_hashable
  31. from pandas.core.dtypes.missing import isna, notna
  32. import pandas as pd
  33. from pandas.core import config, missing, nanops
  34. import pandas.core.algorithms as algos
  35. from pandas.core.base import PandasObject, SelectionMixin
  36. import pandas.core.common as com
  37. from pandas.core.index import (
  38. Index, InvalidIndexError, MultiIndex, RangeIndex, ensure_index)
  39. from pandas.core.indexes.datetimes import DatetimeIndex
  40. from pandas.core.indexes.period import Period, PeriodIndex
  41. import pandas.core.indexing as indexing
  42. from pandas.core.internals import BlockManager
  43. from pandas.core.ops import _align_method_FRAME
  44. from pandas.io.formats.format import DataFrameFormatter, format_percentiles
  45. from pandas.io.formats.printing import pprint_thing
  46. from pandas.tseries.frequencies import to_offset
  47. # goal is to be able to define the docs close to function, while still being
  48. # able to share
  49. _shared_docs = dict()
  50. _shared_doc_kwargs = dict(
  51. axes='keywords for axes', klass='NDFrame',
  52. axes_single_arg='int or labels for object',
  53. args_transpose='axes to permute (int or label for object)',
  54. optional_by="""
  55. by : str or list of str
  56. Name or list of names to sort by""")
  57. # sentinel value to use as kwarg in place of None when None has special meaning
  58. # and needs to be distinguished from a user explicitly passing None.
  59. sentinel = object()
  60. def _single_replace(self, to_replace, method, inplace, limit):
  61. """
  62. Replaces values in a Series using the fill method specified when no
  63. replacement value is given in the replace method
  64. """
  65. if self.ndim != 1:
  66. raise TypeError('cannot replace {0} with method {1} on a {2}'
  67. .format(to_replace, method, type(self).__name__))
  68. orig_dtype = self.dtype
  69. result = self if inplace else self.copy()
  70. fill_f = missing.get_fill_func(method)
  71. mask = missing.mask_missing(result.values, to_replace)
  72. values = fill_f(result.values, limit=limit, mask=mask)
  73. if values.dtype == orig_dtype and inplace:
  74. return
  75. result = pd.Series(values, index=self.index,
  76. dtype=self.dtype).__finalize__(self)
  77. if inplace:
  78. self._update_inplace(result._data)
  79. return
  80. return result
  81. class NDFrame(PandasObject, SelectionMixin):
  82. """
  83. N-dimensional analogue of DataFrame. Store multi-dimensional in a
  84. size-mutable, labeled data structure
  85. Parameters
  86. ----------
  87. data : BlockManager
  88. axes : list
  89. copy : boolean, default False
  90. """
  91. _internal_names = ['_data', '_cacher', '_item_cache', '_cache', '_is_copy',
  92. '_subtyp', '_name', '_index', '_default_kind',
  93. '_default_fill_value', '_metadata', '__array_struct__',
  94. '__array_interface__']
  95. _internal_names_set = set(_internal_names)
  96. _accessors = frozenset()
  97. _deprecations = frozenset(['as_blocks', 'blocks',
  98. 'convert_objects', 'is_copy'])
  99. _metadata = []
  100. _is_copy = None
  101. # dummy attribute so that datetime.__eq__(Series/DataFrame) defers
  102. # by returning NotImplemented
  103. timetuple = None
  104. # ----------------------------------------------------------------------
  105. # Constructors
  106. def __init__(self, data, axes=None, copy=False, dtype=None,
  107. fastpath=False):
  108. if not fastpath:
  109. if dtype is not None:
  110. data = data.astype(dtype)
  111. elif copy:
  112. data = data.copy()
  113. if axes is not None:
  114. for i, ax in enumerate(axes):
  115. data = data.reindex_axis(ax, axis=i)
  116. object.__setattr__(self, '_is_copy', None)
  117. object.__setattr__(self, '_data', data)
  118. object.__setattr__(self, '_item_cache', {})
  119. def _init_mgr(self, mgr, axes=None, dtype=None, copy=False):
  120. """ passed a manager and a axes dict """
  121. for a, axe in axes.items():
  122. if axe is not None:
  123. mgr = mgr.reindex_axis(axe,
  124. axis=self._get_block_manager_axis(a),
  125. copy=False)
  126. # make a copy if explicitly requested
  127. if copy:
  128. mgr = mgr.copy()
  129. if dtype is not None:
  130. # avoid further copies if we can
  131. if len(mgr.blocks) > 1 or mgr.blocks[0].values.dtype != dtype:
  132. mgr = mgr.astype(dtype=dtype)
  133. return mgr
  134. # ----------------------------------------------------------------------
  135. @property
  136. def is_copy(self):
  137. """
  138. Return the copy.
  139. """
  140. warnings.warn("Attribute 'is_copy' is deprecated and will be removed "
  141. "in a future version.", FutureWarning, stacklevel=2)
  142. return self._is_copy
  143. @is_copy.setter
  144. def is_copy(self, msg):
  145. warnings.warn("Attribute 'is_copy' is deprecated and will be removed "
  146. "in a future version.", FutureWarning, stacklevel=2)
  147. self._is_copy = msg
  148. def _validate_dtype(self, dtype):
  149. """ validate the passed dtype """
  150. if dtype is not None:
  151. dtype = pandas_dtype(dtype)
  152. # a compound dtype
  153. if dtype.kind == 'V':
  154. raise NotImplementedError("compound dtypes are not implemented"
  155. " in the {0} constructor"
  156. .format(self.__class__.__name__))
  157. return dtype
  158. # ----------------------------------------------------------------------
  159. # Construction
  160. @property
  161. def _constructor(self):
  162. """Used when a manipulation result has the same dimensions as the
  163. original.
  164. """
  165. raise AbstractMethodError(self)
  166. @property
  167. def _constructor_sliced(self):
  168. """Used when a manipulation result has one lower dimension(s) as the
  169. original, such as DataFrame single columns slicing.
  170. """
  171. raise AbstractMethodError(self)
  172. @property
  173. def _constructor_expanddim(self):
  174. """Used when a manipulation result has one higher dimension as the
  175. original, such as Series.to_frame() and DataFrame.to_panel()
  176. """
  177. raise NotImplementedError
  178. # ----------------------------------------------------------------------
  179. # Axis
  180. @classmethod
  181. def _setup_axes(cls, axes, info_axis=None, stat_axis=None, aliases=None,
  182. slicers=None, axes_are_reversed=False, build_axes=True,
  183. ns=None, docs=None):
  184. """Provide axes setup for the major PandasObjects.
  185. Parameters
  186. ----------
  187. axes : the names of the axes in order (lowest to highest)
  188. info_axis_num : the axis of the selector dimension (int)
  189. stat_axis_num : the number of axis for the default stats (int)
  190. aliases : other names for a single axis (dict)
  191. slicers : how axes slice to others (dict)
  192. axes_are_reversed : boolean whether to treat passed axes as
  193. reversed (DataFrame)
  194. build_axes : setup the axis properties (default True)
  195. """
  196. cls._AXIS_ORDERS = axes
  197. cls._AXIS_NUMBERS = {a: i for i, a in enumerate(axes)}
  198. cls._AXIS_LEN = len(axes)
  199. cls._AXIS_ALIASES = aliases or dict()
  200. cls._AXIS_IALIASES = {v: k for k, v in cls._AXIS_ALIASES.items()}
  201. cls._AXIS_NAMES = dict(enumerate(axes))
  202. cls._AXIS_SLICEMAP = slicers or None
  203. cls._AXIS_REVERSED = axes_are_reversed
  204. # typ
  205. setattr(cls, '_typ', cls.__name__.lower())
  206. # indexing support
  207. cls._ix = None
  208. if info_axis is not None:
  209. cls._info_axis_number = info_axis
  210. cls._info_axis_name = axes[info_axis]
  211. if stat_axis is not None:
  212. cls._stat_axis_number = stat_axis
  213. cls._stat_axis_name = axes[stat_axis]
  214. # setup the actual axis
  215. if build_axes:
  216. def set_axis(a, i):
  217. setattr(cls, a, properties.AxisProperty(i, docs.get(a, a)))
  218. cls._internal_names_set.add(a)
  219. if axes_are_reversed:
  220. m = cls._AXIS_LEN - 1
  221. for i, a in cls._AXIS_NAMES.items():
  222. set_axis(a, m - i)
  223. else:
  224. for i, a in cls._AXIS_NAMES.items():
  225. set_axis(a, i)
  226. assert not isinstance(ns, dict)
  227. def _construct_axes_dict(self, axes=None, **kwargs):
  228. """Return an axes dictionary for myself."""
  229. d = {a: self._get_axis(a) for a in (axes or self._AXIS_ORDERS)}
  230. d.update(kwargs)
  231. return d
  232. @staticmethod
  233. def _construct_axes_dict_from(self, axes, **kwargs):
  234. """Return an axes dictionary for the passed axes."""
  235. d = {a: ax for a, ax in zip(self._AXIS_ORDERS, axes)}
  236. d.update(kwargs)
  237. return d
  238. def _construct_axes_dict_for_slice(self, axes=None, **kwargs):
  239. """Return an axes dictionary for myself."""
  240. d = {self._AXIS_SLICEMAP[a]: self._get_axis(a)
  241. for a in (axes or self._AXIS_ORDERS)}
  242. d.update(kwargs)
  243. return d
  244. def _construct_axes_from_arguments(
  245. self, args, kwargs, require_all=False, sentinel=None):
  246. """Construct and returns axes if supplied in args/kwargs.
  247. If require_all, raise if all axis arguments are not supplied
  248. return a tuple of (axes, kwargs).
  249. sentinel specifies the default parameter when an axis is not
  250. supplied; useful to distinguish when a user explicitly passes None
  251. in scenarios where None has special meaning.
  252. """
  253. # construct the args
  254. args = list(args)
  255. for a in self._AXIS_ORDERS:
  256. # if we have an alias for this axis
  257. alias = self._AXIS_IALIASES.get(a)
  258. if alias is not None:
  259. if a in kwargs:
  260. if alias in kwargs:
  261. raise TypeError("arguments are mutually exclusive "
  262. "for [%s,%s]" % (a, alias))
  263. continue
  264. if alias in kwargs:
  265. kwargs[a] = kwargs.pop(alias)
  266. continue
  267. # look for a argument by position
  268. if a not in kwargs:
  269. try:
  270. kwargs[a] = args.pop(0)
  271. except IndexError:
  272. if require_all:
  273. raise TypeError("not enough/duplicate arguments "
  274. "specified!")
  275. axes = {a: kwargs.pop(a, sentinel) for a in self._AXIS_ORDERS}
  276. return axes, kwargs
  277. @classmethod
  278. def _from_axes(cls, data, axes, **kwargs):
  279. # for construction from BlockManager
  280. if isinstance(data, BlockManager):
  281. return cls(data, **kwargs)
  282. else:
  283. if cls._AXIS_REVERSED:
  284. axes = axes[::-1]
  285. d = cls._construct_axes_dict_from(cls, axes, copy=False)
  286. d.update(kwargs)
  287. return cls(data, **d)
  288. @classmethod
  289. def _get_axis_number(cls, axis):
  290. axis = cls._AXIS_ALIASES.get(axis, axis)
  291. if is_integer(axis):
  292. if axis in cls._AXIS_NAMES:
  293. return axis
  294. else:
  295. try:
  296. return cls._AXIS_NUMBERS[axis]
  297. except KeyError:
  298. pass
  299. raise ValueError('No axis named {0} for object type {1}'
  300. .format(axis, cls))
  301. @classmethod
  302. def _get_axis_name(cls, axis):
  303. axis = cls._AXIS_ALIASES.get(axis, axis)
  304. if isinstance(axis, string_types):
  305. if axis in cls._AXIS_NUMBERS:
  306. return axis
  307. else:
  308. try:
  309. return cls._AXIS_NAMES[axis]
  310. except KeyError:
  311. pass
  312. raise ValueError('No axis named {0} for object type {1}'
  313. .format(axis, cls))
  314. def _get_axis(self, axis):
  315. name = self._get_axis_name(axis)
  316. return getattr(self, name)
  317. @classmethod
  318. def _get_block_manager_axis(cls, axis):
  319. """Map the axis to the block_manager axis."""
  320. axis = cls._get_axis_number(axis)
  321. if cls._AXIS_REVERSED:
  322. m = cls._AXIS_LEN - 1
  323. return m - axis
  324. return axis
  325. def _get_axis_resolvers(self, axis):
  326. # index or columns
  327. axis_index = getattr(self, axis)
  328. d = dict()
  329. prefix = axis[0]
  330. for i, name in enumerate(axis_index.names):
  331. if name is not None:
  332. key = level = name
  333. else:
  334. # prefix with 'i' or 'c' depending on the input axis
  335. # e.g., you must do ilevel_0 for the 0th level of an unnamed
  336. # multiiindex
  337. key = '{prefix}level_{i}'.format(prefix=prefix, i=i)
  338. level = i
  339. level_values = axis_index.get_level_values(level)
  340. s = level_values.to_series()
  341. s.index = axis_index
  342. d[key] = s
  343. # put the index/columns itself in the dict
  344. if isinstance(axis_index, MultiIndex):
  345. dindex = axis_index
  346. else:
  347. dindex = axis_index.to_series()
  348. d[axis] = dindex
  349. return d
  350. def _get_index_resolvers(self):
  351. d = {}
  352. for axis_name in self._AXIS_ORDERS:
  353. d.update(self._get_axis_resolvers(axis_name))
  354. return d
  355. @property
  356. def _info_axis(self):
  357. return getattr(self, self._info_axis_name)
  358. @property
  359. def _stat_axis(self):
  360. return getattr(self, self._stat_axis_name)
  361. @property
  362. def shape(self):
  363. """
  364. Return a tuple of axis dimensions
  365. """
  366. return tuple(len(self._get_axis(a)) for a in self._AXIS_ORDERS)
  367. @property
  368. def axes(self):
  369. """
  370. Return index label(s) of the internal NDFrame
  371. """
  372. # we do it this way because if we have reversed axes, then
  373. # the block manager shows then reversed
  374. return [self._get_axis(a) for a in self._AXIS_ORDERS]
  375. @property
  376. def ndim(self):
  377. """
  378. Return an int representing the number of axes / array dimensions.
  379. Return 1 if Series. Otherwise return 2 if DataFrame.
  380. See Also
  381. --------
  382. ndarray.ndim : Number of array dimensions.
  383. Examples
  384. --------
  385. >>> s = pd.Series({'a': 1, 'b': 2, 'c': 3})
  386. >>> s.ndim
  387. 1
  388. >>> df = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})
  389. >>> df.ndim
  390. 2
  391. """
  392. return self._data.ndim
  393. @property
  394. def size(self):
  395. """
  396. Return an int representing the number of elements in this object.
  397. Return the number of rows if Series. Otherwise return the number of
  398. rows times number of columns if DataFrame.
  399. See Also
  400. --------
  401. ndarray.size : Number of elements in the array.
  402. Examples
  403. --------
  404. >>> s = pd.Series({'a': 1, 'b': 2, 'c': 3})
  405. >>> s.size
  406. 3
  407. >>> df = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})
  408. >>> df.size
  409. 4
  410. """
  411. return np.prod(self.shape)
  412. @property
  413. def _selected_obj(self):
  414. """ internal compat with SelectionMixin """
  415. return self
  416. @property
  417. def _obj_with_exclusions(self):
  418. """ internal compat with SelectionMixin """
  419. return self
  420. def _expand_axes(self, key):
  421. new_axes = []
  422. for k, ax in zip(key, self.axes):
  423. if k not in ax:
  424. if type(k) != ax.dtype.type:
  425. ax = ax.astype('O')
  426. new_axes.append(ax.insert(len(ax), k))
  427. else:
  428. new_axes.append(ax)
  429. return new_axes
  430. def set_axis(self, labels, axis=0, inplace=None):
  431. """
  432. Assign desired index to given axis.
  433. Indexes for column or row labels can be changed by assigning
  434. a list-like or Index.
  435. .. versionchanged:: 0.21.0
  436. The signature is now `labels` and `axis`, consistent with
  437. the rest of pandas API. Previously, the `axis` and `labels`
  438. arguments were respectively the first and second positional
  439. arguments.
  440. Parameters
  441. ----------
  442. labels : list-like, Index
  443. The values for the new index.
  444. axis : {0 or 'index', 1 or 'columns'}, default 0
  445. The axis to update. The value 0 identifies the rows, and 1
  446. identifies the columns.
  447. inplace : bool, default None
  448. Whether to return a new %(klass)s instance.
  449. .. warning::
  450. ``inplace=None`` currently falls back to to True, but in a
  451. future version, will default to False. Use inplace=True
  452. explicitly rather than relying on the default.
  453. Returns
  454. -------
  455. renamed : %(klass)s or None
  456. An object of same type as caller if inplace=False, None otherwise.
  457. See Also
  458. --------
  459. DataFrame.rename_axis : Alter the name of the index or columns.
  460. Examples
  461. --------
  462. **Series**
  463. >>> s = pd.Series([1, 2, 3])
  464. >>> s
  465. 0 1
  466. 1 2
  467. 2 3
  468. dtype: int64
  469. >>> s.set_axis(['a', 'b', 'c'], axis=0, inplace=False)
  470. a 1
  471. b 2
  472. c 3
  473. dtype: int64
  474. The original object is not modified.
  475. >>> s
  476. 0 1
  477. 1 2
  478. 2 3
  479. dtype: int64
  480. **DataFrame**
  481. >>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
  482. Change the row labels.
  483. >>> df.set_axis(['a', 'b', 'c'], axis='index', inplace=False)
  484. A B
  485. a 1 4
  486. b 2 5
  487. c 3 6
  488. Change the column labels.
  489. >>> df.set_axis(['I', 'II'], axis='columns', inplace=False)
  490. I II
  491. 0 1 4
  492. 1 2 5
  493. 2 3 6
  494. Now, update the labels inplace.
  495. >>> df.set_axis(['i', 'ii'], axis='columns', inplace=True)
  496. >>> df
  497. i ii
  498. 0 1 4
  499. 1 2 5
  500. 2 3 6
  501. """
  502. if is_scalar(labels):
  503. warnings.warn(
  504. 'set_axis now takes "labels" as first argument, and '
  505. '"axis" as named parameter. The old form, with "axis" as '
  506. 'first parameter and \"labels\" as second, is still supported '
  507. 'but will be deprecated in a future version of pandas.',
  508. FutureWarning, stacklevel=2)
  509. labels, axis = axis, labels
  510. if inplace is None:
  511. warnings.warn(
  512. 'set_axis currently defaults to operating inplace.\nThis '
  513. 'will change in a future version of pandas, use '
  514. 'inplace=True to avoid this warning.',
  515. FutureWarning, stacklevel=2)
  516. inplace = True
  517. if inplace:
  518. setattr(self, self._get_axis_name(axis), labels)
  519. else:
  520. obj = self.copy()
  521. obj.set_axis(labels, axis=axis, inplace=True)
  522. return obj
  523. def _set_axis(self, axis, labels):
  524. self._data.set_axis(axis, labels)
  525. self._clear_item_cache()
  526. def transpose(self, *args, **kwargs):
  527. """
  528. Permute the dimensions of the %(klass)s
  529. Parameters
  530. ----------
  531. args : %(args_transpose)s
  532. copy : boolean, default False
  533. Make a copy of the underlying data. Mixed-dtype data will
  534. always result in a copy
  535. **kwargs
  536. Additional keyword arguments will be passed to the function.
  537. Returns
  538. -------
  539. y : same as input
  540. Examples
  541. --------
  542. >>> p.transpose(2, 0, 1)
  543. >>> p.transpose(2, 0, 1, copy=True)
  544. """
  545. # construct the args
  546. axes, kwargs = self._construct_axes_from_arguments(args, kwargs,
  547. require_all=True)
  548. axes_names = tuple(self._get_axis_name(axes[a])
  549. for a in self._AXIS_ORDERS)
  550. axes_numbers = tuple(self._get_axis_number(axes[a])
  551. for a in self._AXIS_ORDERS)
  552. # we must have unique axes
  553. if len(axes) != len(set(axes)):
  554. raise ValueError('Must specify %s unique axes' % self._AXIS_LEN)
  555. new_axes = self._construct_axes_dict_from(self, [self._get_axis(x)
  556. for x in axes_names])
  557. new_values = self.values.transpose(axes_numbers)
  558. if kwargs.pop('copy', None) or (len(args) and args[-1]):
  559. new_values = new_values.copy()
  560. nv.validate_transpose_for_generic(self, kwargs)
  561. return self._constructor(new_values, **new_axes).__finalize__(self)
  562. def swapaxes(self, axis1, axis2, copy=True):
  563. """
  564. Interchange axes and swap values axes appropriately.
  565. Returns
  566. -------
  567. y : same as input
  568. """
  569. i = self._get_axis_number(axis1)
  570. j = self._get_axis_number(axis2)
  571. if i == j:
  572. if copy:
  573. return self.copy()
  574. return self
  575. mapping = {i: j, j: i}
  576. new_axes = (self._get_axis(mapping.get(k, k))
  577. for k in range(self._AXIS_LEN))
  578. new_values = self.values.swapaxes(i, j)
  579. if copy:
  580. new_values = new_values.copy()
  581. return self._constructor(new_values, *new_axes).__finalize__(self)
  582. def droplevel(self, level, axis=0):
  583. """
  584. Return DataFrame with requested index / column level(s) removed.
  585. .. versionadded:: 0.24.0
  586. Parameters
  587. ----------
  588. level : int, str, or list-like
  589. If a string is given, must be the name of a level
  590. If list-like, elements must be names or positional indexes
  591. of levels.
  592. axis : {0 or 'index', 1 or 'columns'}, default 0
  593. Returns
  594. -------
  595. DataFrame.droplevel()
  596. Examples
  597. --------
  598. >>> df = pd.DataFrame([
  599. ... [1, 2, 3, 4],
  600. ... [5, 6, 7, 8],
  601. ... [9, 10, 11, 12]
  602. ... ]).set_index([0, 1]).rename_axis(['a', 'b'])
  603. >>> df.columns = pd.MultiIndex.from_tuples([
  604. ... ('c', 'e'), ('d', 'f')
  605. ... ], names=['level_1', 'level_2'])
  606. >>> df
  607. level_1 c d
  608. level_2 e f
  609. a b
  610. 1 2 3 4
  611. 5 6 7 8
  612. 9 10 11 12
  613. >>> df.droplevel('a')
  614. level_1 c d
  615. level_2 e f
  616. b
  617. 2 3 4
  618. 6 7 8
  619. 10 11 12
  620. >>> df.droplevel('level2', axis=1)
  621. level_1 c d
  622. a b
  623. 1 2 3 4
  624. 5 6 7 8
  625. 9 10 11 12
  626. """
  627. labels = self._get_axis(axis)
  628. new_labels = labels.droplevel(level)
  629. result = self.set_axis(new_labels, axis=axis, inplace=False)
  630. return result
  631. def pop(self, item):
  632. """
  633. Return item and drop from frame. Raise KeyError if not found.
  634. Parameters
  635. ----------
  636. item : str
  637. Label of column to be popped.
  638. Returns
  639. -------
  640. Series
  641. Examples
  642. --------
  643. >>> df = pd.DataFrame([('falcon', 'bird', 389.0),
  644. ... ('parrot', 'bird', 24.0),
  645. ... ('lion', 'mammal', 80.5),
  646. ... ('monkey','mammal', np.nan)],
  647. ... columns=('name', 'class', 'max_speed'))
  648. >>> df
  649. name class max_speed
  650. 0 falcon bird 389.0
  651. 1 parrot bird 24.0
  652. 2 lion mammal 80.5
  653. 3 monkey mammal NaN
  654. >>> df.pop('class')
  655. 0 bird
  656. 1 bird
  657. 2 mammal
  658. 3 mammal
  659. Name: class, dtype: object
  660. >>> df
  661. name max_speed
  662. 0 falcon 389.0
  663. 1 parrot 24.0
  664. 2 lion 80.5
  665. 3 monkey NaN
  666. """
  667. result = self[item]
  668. del self[item]
  669. try:
  670. result._reset_cacher()
  671. except AttributeError:
  672. pass
  673. return result
  674. def squeeze(self, axis=None):
  675. """
  676. Squeeze 1 dimensional axis objects into scalars.
  677. Series or DataFrames with a single element are squeezed to a scalar.
  678. DataFrames with a single column or a single row are squeezed to a
  679. Series. Otherwise the object is unchanged.
  680. This method is most useful when you don't know if your
  681. object is a Series or DataFrame, but you do know it has just a single
  682. column. In that case you can safely call `squeeze` to ensure you have a
  683. Series.
  684. Parameters
  685. ----------
  686. axis : {0 or 'index', 1 or 'columns', None}, default None
  687. A specific axis to squeeze. By default, all length-1 axes are
  688. squeezed.
  689. .. versionadded:: 0.20.0
  690. Returns
  691. -------
  692. DataFrame, Series, or scalar
  693. The projection after squeezing `axis` or all the axes.
  694. See Also
  695. --------
  696. Series.iloc : Integer-location based indexing for selecting scalars.
  697. DataFrame.iloc : Integer-location based indexing for selecting Series.
  698. Series.to_frame : Inverse of DataFrame.squeeze for a
  699. single-column DataFrame.
  700. Examples
  701. --------
  702. >>> primes = pd.Series([2, 3, 5, 7])
  703. Slicing might produce a Series with a single value:
  704. >>> even_primes = primes[primes % 2 == 0]
  705. >>> even_primes
  706. 0 2
  707. dtype: int64
  708. >>> even_primes.squeeze()
  709. 2
  710. Squeezing objects with more than one value in every axis does nothing:
  711. >>> odd_primes = primes[primes % 2 == 1]
  712. >>> odd_primes
  713. 1 3
  714. 2 5
  715. 3 7
  716. dtype: int64
  717. >>> odd_primes.squeeze()
  718. 1 3
  719. 2 5
  720. 3 7
  721. dtype: int64
  722. Squeezing is even more effective when used with DataFrames.
  723. >>> df = pd.DataFrame([[1, 2], [3, 4]], columns=['a', 'b'])
  724. >>> df
  725. a b
  726. 0 1 2
  727. 1 3 4
  728. Slicing a single column will produce a DataFrame with the columns
  729. having only one value:
  730. >>> df_a = df[['a']]
  731. >>> df_a
  732. a
  733. 0 1
  734. 1 3
  735. So the columns can be squeezed down, resulting in a Series:
  736. >>> df_a.squeeze('columns')
  737. 0 1
  738. 1 3
  739. Name: a, dtype: int64
  740. Slicing a single row from a single column will produce a single
  741. scalar DataFrame:
  742. >>> df_0a = df.loc[df.index < 1, ['a']]
  743. >>> df_0a
  744. a
  745. 0 1
  746. Squeezing the rows produces a single scalar Series:
  747. >>> df_0a.squeeze('rows')
  748. a 1
  749. Name: 0, dtype: int64
  750. Squeezing all axes wil project directly into a scalar:
  751. >>> df_0a.squeeze()
  752. 1
  753. """
  754. axis = (self._AXIS_NAMES if axis is None else
  755. (self._get_axis_number(axis),))
  756. try:
  757. return self.iloc[
  758. tuple(0 if i in axis and len(a) == 1 else slice(None)
  759. for i, a in enumerate(self.axes))]
  760. except Exception:
  761. return self
  762. def swaplevel(self, i=-2, j=-1, axis=0):
  763. """
  764. Swap levels i and j in a MultiIndex on a particular axis
  765. Parameters
  766. ----------
  767. i, j : int, str (can be mixed)
  768. Level of index to be swapped. Can pass level name as string.
  769. Returns
  770. -------
  771. swapped : same type as caller (new object)
  772. .. versionchanged:: 0.18.1
  773. The indexes ``i`` and ``j`` are now optional, and default to
  774. the two innermost levels of the index.
  775. """
  776. axis = self._get_axis_number(axis)
  777. result = self.copy()
  778. labels = result._data.axes[axis]
  779. result._data.set_axis(axis, labels.swaplevel(i, j))
  780. return result
  781. # ----------------------------------------------------------------------
  782. # Rename
  783. def rename(self, *args, **kwargs):
  784. """
  785. Alter axes input function or functions. Function / dict values must be
  786. unique (1-to-1). Labels not contained in a dict / Series will be left
  787. as-is. Extra labels listed don't throw an error. Alternatively, change
  788. ``Series.name`` with a scalar value (Series only).
  789. Parameters
  790. ----------
  791. %(axes)s : scalar, list-like, dict-like or function, optional
  792. Scalar or list-like will alter the ``Series.name`` attribute,
  793. and raise on DataFrame or Panel.
  794. dict-like or functions are transformations to apply to
  795. that axis' values
  796. copy : bool, default True
  797. Also copy underlying data.
  798. inplace : bool, default False
  799. Whether to return a new %(klass)s. If True then value of copy is
  800. ignored.
  801. level : int or level name, default None
  802. In case of a MultiIndex, only rename labels in the specified
  803. level.
  804. errors : {'ignore', 'raise'}, default 'ignore'
  805. If 'raise', raise a `KeyError` when a dict-like `mapper`, `index`,
  806. or `columns` contains labels that are not present in the Index
  807. being transformed.
  808. If 'ignore', existing keys will be renamed and extra keys will be
  809. ignored.
  810. Returns
  811. -------
  812. renamed : %(klass)s (new object)
  813. Raises
  814. ------
  815. KeyError
  816. If any of the labels is not found in the selected axis and
  817. "errors='raise'".
  818. See Also
  819. --------
  820. NDFrame.rename_axis
  821. Examples
  822. --------
  823. >>> s = pd.Series([1, 2, 3])
  824. >>> s
  825. 0 1
  826. 1 2
  827. 2 3
  828. dtype: int64
  829. >>> s.rename("my_name") # scalar, changes Series.name
  830. 0 1
  831. 1 2
  832. 2 3
  833. Name: my_name, dtype: int64
  834. >>> s.rename(lambda x: x ** 2) # function, changes labels
  835. 0 1
  836. 1 2
  837. 4 3
  838. dtype: int64
  839. >>> s.rename({1: 3, 2: 5}) # mapping, changes labels
  840. 0 1
  841. 3 2
  842. 5 3
  843. dtype: int64
  844. Since ``DataFrame`` doesn't have a ``.name`` attribute,
  845. only mapping-type arguments are allowed.
  846. >>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})
  847. >>> df.rename(2)
  848. Traceback (most recent call last):
  849. ...
  850. TypeError: 'int' object is not callable
  851. ``DataFrame.rename`` supports two calling conventions
  852. * ``(index=index_mapper, columns=columns_mapper, ...)``
  853. * ``(mapper, axis={'index', 'columns'}, ...)``
  854. We *highly* recommend using keyword arguments to clarify your
  855. intent.
  856. >>> df.rename(index=str, columns={"A": "a", "B": "c"})
  857. a c
  858. 0 1 4
  859. 1 2 5
  860. 2 3 6
  861. >>> df.rename(index=str, columns={"A": "a", "C": "c"})
  862. a B
  863. 0 1 4
  864. 1 2 5
  865. 2 3 6
  866. Using axis-style parameters
  867. >>> df.rename(str.lower, axis='columns')
  868. a b
  869. 0 1 4
  870. 1 2 5
  871. 2 3 6
  872. >>> df.rename({1: 2, 2: 4}, axis='index')
  873. A B
  874. 0 1 4
  875. 2 2 5
  876. 4 3 6
  877. See the :ref:`user guide <basics.rename>` for more.
  878. """
  879. axes, kwargs = self._construct_axes_from_arguments(args, kwargs)
  880. copy = kwargs.pop('copy', True)
  881. inplace = kwargs.pop('inplace', False)
  882. level = kwargs.pop('level', None)
  883. axis = kwargs.pop('axis', None)
  884. errors = kwargs.pop('errors', 'ignore')
  885. if axis is not None:
  886. # Validate the axis
  887. self._get_axis_number(axis)
  888. if kwargs:
  889. raise TypeError('rename() got an unexpected keyword '
  890. 'argument "{0}"'.format(list(kwargs.keys())[0]))
  891. if com.count_not_none(*axes.values()) == 0:
  892. raise TypeError('must pass an index to rename')
  893. self._consolidate_inplace()
  894. result = self if inplace else self.copy(deep=copy)
  895. # start in the axis order to eliminate too many copies
  896. for axis in lrange(self._AXIS_LEN):
  897. v = axes.get(self._AXIS_NAMES[axis])
  898. if v is None:
  899. continue
  900. f = com._get_rename_function(v)
  901. baxis = self._get_block_manager_axis(axis)
  902. if level is not None:
  903. level = self.axes[axis]._get_level_number(level)
  904. # GH 13473
  905. if not callable(v):
  906. indexer = self.axes[axis].get_indexer_for(v)
  907. if errors == 'raise' and len(indexer[indexer == -1]):
  908. missing_labels = [label for index, label in enumerate(v)
  909. if indexer[index] == -1]
  910. raise KeyError('{} not found in axis'
  911. .format(missing_labels))
  912. result._data = result._data.rename_axis(f, axis=baxis, copy=copy,
  913. level=level)
  914. result._clear_item_cache()
  915. if inplace:
  916. self._update_inplace(result._data)
  917. else:
  918. return result.__finalize__(self)
  919. @rewrite_axis_style_signature('mapper', [('copy', True),
  920. ('inplace', False)])
  921. def rename_axis(self, mapper=sentinel, **kwargs):
  922. """
  923. Set the name of the axis for the index or columns.
  924. Parameters
  925. ----------
  926. mapper : scalar, list-like, optional
  927. Value to set the axis name attribute.
  928. index, columns : scalar, list-like, dict-like or function, optional
  929. A scalar, list-like, dict-like or functions transformations to
  930. apply to that axis' values.
  931. Use either ``mapper`` and ``axis`` to
  932. specify the axis to target with ``mapper``, or ``index``
  933. and/or ``columns``.
  934. .. versionchanged:: 0.24.0
  935. axis : {0 or 'index', 1 or 'columns'}, default 0
  936. The axis to rename.
  937. copy : bool, default True
  938. Also copy underlying data.
  939. inplace : bool, default False
  940. Modifies the object directly, instead of creating a new Series
  941. or DataFrame.
  942. Returns
  943. -------
  944. Series, DataFrame, or None
  945. The same type as the caller or None if `inplace` is True.
  946. See Also
  947. --------
  948. Series.rename : Alter Series index labels or name.
  949. DataFrame.rename : Alter DataFrame index labels or name.
  950. Index.rename : Set new names on index.
  951. Notes
  952. -----
  953. Prior to version 0.21.0, ``rename_axis`` could also be used to change
  954. the axis *labels* by passing a mapping or scalar. This behavior is
  955. deprecated and will be removed in a future version. Use ``rename``
  956. instead.
  957. ``DataFrame.rename_axis`` supports two calling conventions
  958. * ``(index=index_mapper, columns=columns_mapper, ...)``
  959. * ``(mapper, axis={'index', 'columns'}, ...)``
  960. The first calling convention will only modify the names of
  961. the index and/or the names of the Index object that is the columns.
  962. In this case, the parameter ``copy`` is ignored.
  963. The second calling convention will modify the names of the
  964. the corresponding index if mapper is a list or a scalar.
  965. However, if mapper is dict-like or a function, it will use the
  966. deprecated behavior of modifying the axis *labels*.
  967. We *highly* recommend using keyword arguments to clarify your
  968. intent.
  969. Examples
  970. --------
  971. **Series**
  972. >>> s = pd.Series(["dog", "cat", "monkey"])
  973. >>> s
  974. 0 dog
  975. 1 cat
  976. 2 monkey
  977. dtype: object
  978. >>> s.rename_axis("animal")
  979. animal
  980. 0 dog
  981. 1 cat
  982. 2 monkey
  983. dtype: object
  984. **DataFrame**
  985. >>> df = pd.DataFrame({"num_legs": [4, 4, 2],
  986. ... "num_arms": [0, 0, 2]},
  987. ... ["dog", "cat", "monkey"])
  988. >>> df
  989. num_legs num_arms
  990. dog 4 0
  991. cat 4 0
  992. monkey 2 2
  993. >>> df = df.rename_axis("animal")
  994. >>> df
  995. num_legs num_arms
  996. animal
  997. dog 4 0
  998. cat 4 0
  999. monkey 2 2
  1000. >>> df = df.rename_axis("limbs", axis="columns")
  1001. >>> df
  1002. limbs num_legs num_arms
  1003. animal
  1004. dog 4 0
  1005. cat 4 0
  1006. monkey 2 2
  1007. **MultiIndex**
  1008. >>> df.index = pd.MultiIndex.from_product([['mammal'],
  1009. ... ['dog', 'cat', 'monkey']],
  1010. ... names=['type', 'name'])
  1011. >>> df
  1012. limbs num_legs num_arms
  1013. type name
  1014. mammal dog 4 0
  1015. cat 4 0
  1016. monkey 2 2
  1017. >>> df.rename_axis(index={'type': 'class'})
  1018. limbs num_legs num_arms
  1019. class name
  1020. mammal dog 4 0
  1021. cat 4 0
  1022. monkey 2 2
  1023. >>> df.rename_axis(columns=str.upper)
  1024. LIMBS num_legs num_arms
  1025. type name
  1026. mammal dog 4 0
  1027. cat 4 0
  1028. monkey 2 2
  1029. """
  1030. axes, kwargs = self._construct_axes_from_arguments(
  1031. (), kwargs, sentinel=sentinel)
  1032. copy = kwargs.pop('copy', True)
  1033. inplace = kwargs.pop('inplace', False)
  1034. axis = kwargs.pop('axis', 0)
  1035. if axis is not None:
  1036. axis = self._get_axis_number(axis)
  1037. if kwargs:
  1038. raise TypeError('rename_axis() got an unexpected keyword '
  1039. 'argument "{0}"'.format(list(kwargs.keys())[0]))
  1040. inplace = validate_bool_kwarg(inplace, 'inplace')
  1041. if (mapper is not sentinel):
  1042. # Use v0.23 behavior if a scalar or list
  1043. non_mapper = is_scalar(mapper) or (is_list_like(mapper) and not
  1044. is_dict_like(mapper))
  1045. if non_mapper:
  1046. return self._set_axis_name(mapper, axis=axis, inplace=inplace)
  1047. else:
  1048. # Deprecated (v0.21) behavior is if mapper is specified,
  1049. # and not a list or scalar, then call rename
  1050. msg = ("Using 'rename_axis' to alter labels is deprecated. "
  1051. "Use '.rename' instead")
  1052. warnings.warn(msg, FutureWarning, stacklevel=3)
  1053. axis = self._get_axis_name(axis)
  1054. d = {'copy': copy, 'inplace': inplace}
  1055. d[axis] = mapper
  1056. return self.rename(**d)
  1057. else:
  1058. # Use new behavior. Means that index and/or columns
  1059. # is specified
  1060. result = self if inplace else self.copy(deep=copy)
  1061. for axis in lrange(self._AXIS_LEN):
  1062. v = axes.get(self._AXIS_NAMES[axis])
  1063. if v is sentinel:
  1064. continue
  1065. non_mapper = is_scalar(v) or (is_list_like(v) and not
  1066. is_dict_like(v))
  1067. if non_mapper:
  1068. newnames = v
  1069. else:
  1070. f = com._get_rename_function(v)
  1071. curnames = self._get_axis(axis).names
  1072. newnames = [f(name) for name in curnames]
  1073. result._set_axis_name(newnames, axis=axis,
  1074. inplace=True)
  1075. if not inplace:
  1076. return result
  1077. def _set_axis_name(self, name, axis=0, inplace=False):
  1078. """
  1079. Set the name(s) of the axis.
  1080. Parameters
  1081. ----------
  1082. name : str or list of str
  1083. Name(s) to set.
  1084. axis : {0 or 'index', 1 or 'columns'}, default 0
  1085. The axis to set the label. The value 0 or 'index' specifies index,
  1086. and the value 1 or 'columns' specifies columns.
  1087. inplace : bool, default False
  1088. If `True`, do operation inplace and return None.
  1089. .. versionadded:: 0.21.0
  1090. Returns
  1091. -------
  1092. Series, DataFrame, or None
  1093. The same type as the caller or `None` if `inplace` is `True`.
  1094. See Also
  1095. --------
  1096. DataFrame.rename : Alter the axis labels of :class:`DataFrame`.
  1097. Series.rename : Alter the index labels or set the index name
  1098. of :class:`Series`.
  1099. Index.rename : Set the name of :class:`Index` or :class:`MultiIndex`.
  1100. Examples
  1101. --------
  1102. >>> df = pd.DataFrame({"num_legs": [4, 4, 2]},
  1103. ... ["dog", "cat", "monkey"])
  1104. >>> df
  1105. num_legs
  1106. dog 4
  1107. cat 4
  1108. monkey 2
  1109. >>> df._set_axis_name("animal")
  1110. num_legs
  1111. animal
  1112. dog 4
  1113. cat 4
  1114. monkey 2
  1115. >>> df.index = pd.MultiIndex.from_product(
  1116. ... [["mammal"], ['dog', 'cat', 'monkey']])
  1117. >>> df._set_axis_name(["type", "name"])
  1118. legs
  1119. type name
  1120. mammal dog 4
  1121. cat 4
  1122. monkey 2
  1123. """
  1124. axis = self._get_axis_number(axis)
  1125. idx = self._get_axis(axis).set_names(name)
  1126. inplace = validate_bool_kwarg(inplace, 'inplace')
  1127. renamed = self if inplace else self.copy()
  1128. renamed.set_axis(idx, axis=axis, inplace=True)
  1129. if not inplace:
  1130. return renamed
  1131. # ----------------------------------------------------------------------
  1132. # Comparison Methods
  1133. def _indexed_same(self, other):
  1134. return all(self._get_axis(a).equals(other._get_axis(a))
  1135. for a in self._AXIS_ORDERS)
  1136. def equals(self, other):
  1137. """
  1138. Test whether two objects contain the same elements.
  1139. This function allows two Series or DataFrames to be compared against
  1140. each other to see if they have the same shape and elements. NaNs in
  1141. the same location are considered equal. The column headers do not
  1142. need to have the same type, but the elements within the columns must
  1143. be the same dtype.
  1144. Parameters
  1145. ----------
  1146. other : Series or DataFrame
  1147. The other Series or DataFrame to be compared with the first.
  1148. Returns
  1149. -------
  1150. bool
  1151. True if all elements are the same in both objects, False
  1152. otherwise.
  1153. See Also
  1154. --------
  1155. Series.eq : Compare two Series objects of the same length
  1156. and return a Series where each element is True if the element
  1157. in each Series is equal, False otherwise.
  1158. DataFrame.eq : Compare two DataFrame objects of the same shape and
  1159. return a DataFrame where each element is True if the respective
  1160. element in each DataFrame is equal, False otherwise.
  1161. assert_series_equal : Return True if left and right Series are equal,
  1162. False otherwise.
  1163. assert_frame_equal : Return True if left and right DataFrames are
  1164. equal, False otherwise.
  1165. numpy.array_equal : Return True if two arrays have the same shape
  1166. and elements, False otherwise.
  1167. Notes
  1168. -----
  1169. This function requires that the elements have the same dtype as their
  1170. respective elements in the other Series or DataFrame. However, the
  1171. column labels do not need to have the same type, as long as they are
  1172. still considered equal.
  1173. Examples
  1174. --------
  1175. >>> df = pd.DataFrame({1: [10], 2: [20]})
  1176. >>> df
  1177. 1 2
  1178. 0 10 20
  1179. DataFrames df and exactly_equal have the same types and values for
  1180. their elements and column labels, which will return True.
  1181. >>> exactly_equal = pd.DataFrame({1: [10], 2: [20]})
  1182. >>> exactly_equal
  1183. 1 2
  1184. 0 10 20
  1185. >>> df.equals(exactly_equal)
  1186. True
  1187. DataFrames df and different_column_type have the same element
  1188. types and values, but have different types for the column labels,
  1189. which will still return True.
  1190. >>> different_column_type = pd.DataFrame({1.0: [10], 2.0: [20]})
  1191. >>> different_column_type
  1192. 1.0 2.0
  1193. 0 10 20
  1194. >>> df.equals(different_column_type)
  1195. True
  1196. DataFrames df and different_data_type have different types for the
  1197. same values for their elements, and will return False even though
  1198. their column labels are the same values and types.
  1199. >>> different_data_type = pd.DataFrame({1: [10.0], 2: [20.0]})
  1200. >>> different_data_type
  1201. 1 2
  1202. 0 10.0 20.0
  1203. >>> df.equals(different_data_type)
  1204. False
  1205. """
  1206. if not isinstance(other, self._constructor):
  1207. return False
  1208. return self._data.equals(other._data)
  1209. # -------------------------------------------------------------------------
  1210. # Unary Methods
  1211. def __neg__(self):
  1212. values = com.values_from_object(self)
  1213. if is_bool_dtype(values):
  1214. arr = operator.inv(values)
  1215. elif (is_numeric_dtype(values) or is_timedelta64_dtype(values)
  1216. or is_object_dtype(values)):
  1217. arr = operator.neg(values)
  1218. else:
  1219. raise TypeError("Unary negative expects numeric dtype, not {}"
  1220. .format(values.dtype))
  1221. return self.__array_wrap__(arr)
  1222. def __pos__(self):
  1223. values = com.values_from_object(self)
  1224. if (is_bool_dtype(values) or is_period_arraylike(values)):
  1225. arr = values
  1226. elif (is_numeric_dtype(values) or is_timedelta64_dtype(values)
  1227. or is_object_dtype(values)):
  1228. arr = operator.pos(values)
  1229. else:
  1230. raise TypeError("Unary plus expects numeric dtype, not {}"
  1231. .format(values.dtype))
  1232. return self.__array_wrap__(arr)
  1233. def __invert__(self):
  1234. try:
  1235. arr = operator.inv(com.values_from_object(self))
  1236. return self.__array_wrap__(arr)
  1237. except Exception:
  1238. # inv fails with 0 len
  1239. if not np.prod(self.shape):
  1240. return self
  1241. raise
  1242. def __nonzero__(self):
  1243. raise ValueError("The truth value of a {0} is ambiguous. "
  1244. "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
  1245. .format(self.__class__.__name__))
  1246. __bool__ = __nonzero__
  1247. def bool(self):
  1248. """
  1249. Return the bool of a single element PandasObject.
  1250. This must be a boolean scalar value, either True or False. Raise a
  1251. ValueError if the PandasObject does not have exactly 1 element, or that
  1252. element is not boolean
  1253. """
  1254. v = self.squeeze()
  1255. if isinstance(v, (bool, np.bool_)):
  1256. return bool(v)
  1257. elif is_scalar(v):
  1258. raise ValueError("bool cannot act on a non-boolean single element "
  1259. "{0}".format(self.__class__.__name__))
  1260. self.__nonzero__()
  1261. def __abs__(self):
  1262. return self.abs()
  1263. def __round__(self, decimals=0):
  1264. return self.round(decimals)
  1265. # -------------------------------------------------------------------------
  1266. # Label or Level Combination Helpers
  1267. #
  1268. # A collection of helper methods for DataFrame/Series operations that
  1269. # accept a combination of column/index labels and levels. All such
  1270. # operations should utilize/extend these methods when possible so that we
  1271. # have consistent precedence and validation logic throughout the library.
  1272. def _is_level_reference(self, key, axis=0):
  1273. """
  1274. Test whethe

Large files files are truncated, but you can click here to view the full file