panel.py 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588
  1. """
  2. Contains data structures designed for manipulating panel (3-dimensional) data
  3. """
  4. # pylint: disable=E1103,W0231,W0212,W0621
  5. from __future__ import division
  6. import warnings
  7. import numpy as np
  8. import pandas.compat as compat
  9. from pandas.compat import OrderedDict, map, range, u, zip
  10. from pandas.compat.numpy import function as nv
  11. from pandas.util._decorators import Appender, Substitution, deprecate_kwarg
  12. from pandas.util._validators import validate_axis_style_args
  13. from pandas.core.dtypes.cast import (
  14. cast_scalar_to_array, infer_dtype_from_scalar, maybe_cast_item)
  15. from pandas.core.dtypes.common import (
  16. is_integer, is_list_like, is_scalar, is_string_like)
  17. from pandas.core.dtypes.missing import notna
  18. import pandas.core.common as com
  19. from pandas.core.frame import DataFrame
  20. from pandas.core.generic import NDFrame, _shared_docs
  21. from pandas.core.index import (
  22. Index, MultiIndex, _get_objs_combined_axis, ensure_index)
  23. import pandas.core.indexes.base as ibase
  24. from pandas.core.indexing import maybe_droplevels
  25. from pandas.core.internals import (
  26. BlockManager, create_block_manager_from_arrays,
  27. create_block_manager_from_blocks)
  28. import pandas.core.ops as ops
  29. from pandas.core.reshape.util import cartesian_product
  30. from pandas.core.series import Series
  31. from pandas.io.formats.printing import pprint_thing
  32. _shared_doc_kwargs = dict(
  33. axes='items, major_axis, minor_axis',
  34. klass="Panel",
  35. axes_single_arg="{0, 1, 2, 'items', 'major_axis', 'minor_axis'}",
  36. optional_mapper='', optional_axis='', optional_labels='')
  37. _shared_doc_kwargs['args_transpose'] = (
  38. "three positional arguments: each one of\n{ax_single}".format(
  39. ax_single=_shared_doc_kwargs['axes_single_arg']))
  40. def _ensure_like_indices(time, panels):
  41. """
  42. Makes sure that time and panels are conformable.
  43. """
  44. n_time = len(time)
  45. n_panel = len(panels)
  46. u_panels = np.unique(panels) # this sorts!
  47. u_time = np.unique(time)
  48. if len(u_time) == n_time:
  49. time = np.tile(u_time, len(u_panels))
  50. if len(u_panels) == n_panel:
  51. panels = np.repeat(u_panels, len(u_time))
  52. return time, panels
  53. def panel_index(time, panels, names=None):
  54. """
  55. Returns a multi-index suitable for a panel-like DataFrame.
  56. Parameters
  57. ----------
  58. time : array-like
  59. Time index, does not have to repeat
  60. panels : array-like
  61. Panel index, does not have to repeat
  62. names : list, optional
  63. List containing the names of the indices
  64. Returns
  65. -------
  66. multi_index : MultiIndex
  67. Time index is the first level, the panels are the second level.
  68. Examples
  69. --------
  70. >>> years = range(1960,1963)
  71. >>> panels = ['A', 'B', 'C']
  72. >>> panel_idx = panel_index(years, panels)
  73. >>> panel_idx
  74. MultiIndex([(1960, 'A'), (1961, 'A'), (1962, 'A'), (1960, 'B'),
  75. (1961, 'B'), (1962, 'B'), (1960, 'C'), (1961, 'C'),
  76. (1962, 'C')], dtype=object)
  77. or
  78. >>> years = np.repeat(range(1960,1963), 3)
  79. >>> panels = np.tile(['A', 'B', 'C'], 3)
  80. >>> panel_idx = panel_index(years, panels)
  81. >>> panel_idx
  82. MultiIndex([(1960, 'A'), (1960, 'B'), (1960, 'C'), (1961, 'A'),
  83. (1961, 'B'), (1961, 'C'), (1962, 'A'), (1962, 'B'),
  84. (1962, 'C')], dtype=object)
  85. """
  86. if names is None:
  87. names = ['time', 'panel']
  88. time, panels = _ensure_like_indices(time, panels)
  89. return MultiIndex.from_arrays([time, panels], sortorder=None, names=names)
  90. class Panel(NDFrame):
  91. """
  92. Represents wide format panel data, stored as 3-dimensional array.
  93. .. deprecated:: 0.20.0
  94. The recommended way to represent 3-D data are with a MultiIndex on a
  95. DataFrame via the :attr:`~Panel.to_frame()` method or with the
  96. `xarray package <http://xarray.pydata.org/en/stable/>`__.
  97. Pandas provides a :attr:`~Panel.to_xarray()` method to automate this
  98. conversion.
  99. Parameters
  100. ----------
  101. data : ndarray (items x major x minor), or dict of DataFrames
  102. items : Index or array-like
  103. axis=0
  104. major_axis : Index or array-like
  105. axis=1
  106. minor_axis : Index or array-like
  107. axis=2
  108. copy : boolean, default False
  109. Copy data from inputs. Only affects DataFrame / 2d ndarray input
  110. dtype : dtype, default None
  111. Data type to force, otherwise infer
  112. """
  113. @property
  114. def _constructor(self):
  115. return type(self)
  116. _constructor_sliced = DataFrame
  117. def __init__(self, data=None, items=None, major_axis=None, minor_axis=None,
  118. copy=False, dtype=None):
  119. # deprecation GH13563
  120. warnings.warn("\nPanel is deprecated and will be removed in a "
  121. "future version.\nThe recommended way to represent "
  122. "these types of 3-dimensional data are with a "
  123. "MultiIndex on a DataFrame, via the "
  124. "Panel.to_frame() method\n"
  125. "Alternatively, you can use the xarray package "
  126. "http://xarray.pydata.org/en/stable/.\n"
  127. "Pandas provides a `.to_xarray()` method to help "
  128. "automate this conversion.\n",
  129. FutureWarning, stacklevel=3)
  130. self._init_data(data=data, items=items, major_axis=major_axis,
  131. minor_axis=minor_axis, copy=copy, dtype=dtype)
  132. def _init_data(self, data, copy, dtype, **kwargs):
  133. """
  134. Generate ND initialization; axes are passed
  135. as required objects to __init__.
  136. """
  137. if data is None:
  138. data = {}
  139. if dtype is not None:
  140. dtype = self._validate_dtype(dtype)
  141. passed_axes = [kwargs.pop(a, None) for a in self._AXIS_ORDERS]
  142. if kwargs:
  143. raise TypeError('_init_data() got an unexpected keyword '
  144. 'argument "{0}"'.format(list(kwargs.keys())[0]))
  145. axes = None
  146. if isinstance(data, BlockManager):
  147. if com._any_not_none(*passed_axes):
  148. axes = [x if x is not None else y
  149. for x, y in zip(passed_axes, data.axes)]
  150. mgr = data
  151. elif isinstance(data, dict):
  152. mgr = self._init_dict(data, passed_axes, dtype=dtype)
  153. copy = False
  154. dtype = None
  155. elif isinstance(data, (np.ndarray, list)):
  156. mgr = self._init_matrix(data, passed_axes, dtype=dtype, copy=copy)
  157. copy = False
  158. dtype = None
  159. elif is_scalar(data) and com._all_not_none(*passed_axes):
  160. values = cast_scalar_to_array([len(x) for x in passed_axes],
  161. data, dtype=dtype)
  162. mgr = self._init_matrix(values, passed_axes, dtype=values.dtype,
  163. copy=False)
  164. copy = False
  165. else: # pragma: no cover
  166. raise ValueError('Panel constructor not properly called!')
  167. NDFrame.__init__(self, mgr, axes=axes, copy=copy, dtype=dtype)
  168. def _init_dict(self, data, axes, dtype=None):
  169. haxis = axes.pop(self._info_axis_number)
  170. # prefilter if haxis passed
  171. if haxis is not None:
  172. haxis = ensure_index(haxis)
  173. data = OrderedDict((k, v)
  174. for k, v in compat.iteritems(data)
  175. if k in haxis)
  176. else:
  177. keys = com.dict_keys_to_ordered_list(data)
  178. haxis = Index(keys)
  179. for k, v in compat.iteritems(data):
  180. if isinstance(v, dict):
  181. data[k] = self._constructor_sliced(v)
  182. # extract axis for remaining axes & create the slicemap
  183. raxes = [self._extract_axis(self, data, axis=i) if a is None else a
  184. for i, a in enumerate(axes)]
  185. raxes_sm = self._extract_axes_for_slice(self, raxes)
  186. # shallow copy
  187. arrays = []
  188. haxis_shape = [len(a) for a in raxes]
  189. for h in haxis:
  190. v = values = data.get(h)
  191. if v is None:
  192. values = np.empty(haxis_shape, dtype=dtype)
  193. values.fill(np.nan)
  194. elif isinstance(v, self._constructor_sliced):
  195. d = raxes_sm.copy()
  196. d['copy'] = False
  197. v = v.reindex(**d)
  198. if dtype is not None:
  199. v = v.astype(dtype)
  200. values = v.values
  201. arrays.append(values)
  202. return self._init_arrays(arrays, haxis, [haxis] + raxes)
  203. def _init_arrays(self, arrays, arr_names, axes):
  204. return create_block_manager_from_arrays(arrays, arr_names, axes)
  205. @classmethod
  206. def from_dict(cls, data, intersect=False, orient='items', dtype=None):
  207. """
  208. Construct Panel from dict of DataFrame objects.
  209. Parameters
  210. ----------
  211. data : dict
  212. {field : DataFrame}
  213. intersect : boolean
  214. Intersect indexes of input DataFrames
  215. orient : {'items', 'minor'}, default 'items'
  216. The "orientation" of the data. If the keys of the passed dict
  217. should be the items of the result panel, pass 'items'
  218. (default). Otherwise if the columns of the values of the passed
  219. DataFrame objects should be the items (which in the case of
  220. mixed-dtype data you should do), instead pass 'minor'
  221. dtype : dtype, default None
  222. Data type to force, otherwise infer
  223. Returns
  224. -------
  225. Panel
  226. """
  227. from collections import defaultdict
  228. orient = orient.lower()
  229. if orient == 'minor':
  230. new_data = defaultdict(OrderedDict)
  231. for col, df in compat.iteritems(data):
  232. for item, s in compat.iteritems(df):
  233. new_data[item][col] = s
  234. data = new_data
  235. elif orient != 'items': # pragma: no cover
  236. raise ValueError('Orientation must be one of {items, minor}.')
  237. d = cls._homogenize_dict(cls, data, intersect=intersect, dtype=dtype)
  238. ks = list(d['data'].keys())
  239. if not isinstance(d['data'], OrderedDict):
  240. ks = list(sorted(ks))
  241. d[cls._info_axis_name] = Index(ks)
  242. return cls(**d)
  243. def __getitem__(self, key):
  244. key = com.apply_if_callable(key, self)
  245. if isinstance(self._info_axis, MultiIndex):
  246. return self._getitem_multilevel(key)
  247. if not (is_list_like(key) or isinstance(key, slice)):
  248. return super(Panel, self).__getitem__(key)
  249. return self.loc[key]
  250. def _getitem_multilevel(self, key):
  251. info = self._info_axis
  252. loc = info.get_loc(key)
  253. if isinstance(loc, (slice, np.ndarray)):
  254. new_index = info[loc]
  255. result_index = maybe_droplevels(new_index, key)
  256. slices = [loc] + [slice(None)] * (self._AXIS_LEN - 1)
  257. new_values = self.values[slices]
  258. d = self._construct_axes_dict(self._AXIS_ORDERS[1:])
  259. d[self._info_axis_name] = result_index
  260. result = self._constructor(new_values, **d)
  261. return result
  262. else:
  263. return self._get_item_cache(key)
  264. def _init_matrix(self, data, axes, dtype=None, copy=False):
  265. values = self._prep_ndarray(self, data, copy=copy)
  266. if dtype is not None:
  267. try:
  268. values = values.astype(dtype)
  269. except Exception:
  270. raise ValueError('failed to cast to '
  271. '{datatype}'.format(datatype=dtype))
  272. shape = values.shape
  273. fixed_axes = []
  274. for i, ax in enumerate(axes):
  275. if ax is None:
  276. ax = ibase.default_index(shape[i])
  277. else:
  278. ax = ensure_index(ax)
  279. fixed_axes.append(ax)
  280. return create_block_manager_from_blocks([values], fixed_axes)
  281. # ----------------------------------------------------------------------
  282. # Comparison methods
  283. def _compare_constructor(self, other, func):
  284. if not self._indexed_same(other):
  285. raise Exception('Can only compare identically-labeled '
  286. 'same type objects')
  287. new_data = {col: func(self[col], other[col])
  288. for col in self._info_axis}
  289. d = self._construct_axes_dict(copy=False)
  290. return self._constructor(data=new_data, **d)
  291. # ----------------------------------------------------------------------
  292. # Magic methods
  293. def __unicode__(self):
  294. """
  295. Return a string representation for a particular Panel.
  296. Invoked by unicode(df) in py2 only.
  297. Yields a Unicode String in both py2/py3.
  298. """
  299. class_name = str(self.__class__)
  300. dims = u('Dimensions: {dimensions}'.format(dimensions=' x '.join(
  301. ["{shape} ({axis})".format(shape=shape, axis=axis) for axis, shape
  302. in zip(self._AXIS_ORDERS, self.shape)])))
  303. def axis_pretty(a):
  304. v = getattr(self, a)
  305. if len(v) > 0:
  306. return u('{ax} axis: {x} to {y}'.format(ax=a.capitalize(),
  307. x=pprint_thing(v[0]),
  308. y=pprint_thing(v[-1])))
  309. else:
  310. return u('{ax} axis: None'.format(ax=a.capitalize()))
  311. output = '\n'.join(
  312. [class_name, dims] + [axis_pretty(a) for a in self._AXIS_ORDERS])
  313. return output
  314. def _get_plane_axes_index(self, axis):
  315. """
  316. Get my plane axes indexes: these are already
  317. (as compared with higher level planes),
  318. as we are returning a DataFrame axes indexes.
  319. """
  320. axis_name = self._get_axis_name(axis)
  321. if axis_name == 'major_axis':
  322. index = 'minor_axis'
  323. columns = 'items'
  324. if axis_name == 'minor_axis':
  325. index = 'major_axis'
  326. columns = 'items'
  327. elif axis_name == 'items':
  328. index = 'major_axis'
  329. columns = 'minor_axis'
  330. return index, columns
  331. def _get_plane_axes(self, axis):
  332. """
  333. Get my plane axes indexes: these are already
  334. (as compared with higher level planes),
  335. as we are returning a DataFrame axes.
  336. """
  337. return [self._get_axis(axi)
  338. for axi in self._get_plane_axes_index(axis)]
  339. fromDict = from_dict
  340. def to_sparse(self, *args, **kwargs):
  341. """
  342. NOT IMPLEMENTED: do not call this method, as sparsifying is not
  343. supported for Panel objects and will raise an error.
  344. Convert to SparsePanel.
  345. """
  346. raise NotImplementedError("sparsifying is not supported "
  347. "for Panel objects")
  348. def to_excel(self, path, na_rep='', engine=None, **kwargs):
  349. """
  350. Write each DataFrame in Panel to a separate excel sheet.
  351. Parameters
  352. ----------
  353. path : string or ExcelWriter object
  354. File path or existing ExcelWriter
  355. na_rep : string, default ''
  356. Missing data representation
  357. engine : string, default None
  358. write engine to use - you can also set this via the options
  359. ``io.excel.xlsx.writer``, ``io.excel.xls.writer``, and
  360. ``io.excel.xlsm.writer``.
  361. Other Parameters
  362. ----------------
  363. float_format : string, default None
  364. Format string for floating point numbers
  365. cols : sequence, optional
  366. Columns to write
  367. header : boolean or list of string, default True
  368. Write out column names. If a list of string is given it is
  369. assumed to be aliases for the column names
  370. index : boolean, default True
  371. Write row names (index)
  372. index_label : string or sequence, default None
  373. Column label for index column(s) if desired. If None is given, and
  374. `header` and `index` are True, then the index names are used. A
  375. sequence should be given if the DataFrame uses MultiIndex.
  376. startrow : upper left cell row to dump data frame
  377. startcol : upper left cell column to dump data frame
  378. Notes
  379. -----
  380. Keyword arguments (and na_rep) are passed to the ``to_excel`` method
  381. for each DataFrame written.
  382. """
  383. from pandas.io.excel import ExcelWriter
  384. if isinstance(path, compat.string_types):
  385. writer = ExcelWriter(path, engine=engine)
  386. else:
  387. writer = path
  388. kwargs['na_rep'] = na_rep
  389. for item, df in self.iteritems():
  390. name = str(item)
  391. df.to_excel(writer, name, **kwargs)
  392. writer.save()
  393. def as_matrix(self):
  394. self._consolidate_inplace()
  395. return self._data.as_array()
  396. # ----------------------------------------------------------------------
  397. # Getting and setting elements
  398. def get_value(self, *args, **kwargs):
  399. """
  400. Quickly retrieve single value at (item, major, minor) location.
  401. .. deprecated:: 0.21.0
  402. Please use .at[] or .iat[] accessors.
  403. Parameters
  404. ----------
  405. item : item label (panel item)
  406. major : major axis label (panel item row)
  407. minor : minor axis label (panel item column)
  408. takeable : interpret the passed labels as indexers, default False
  409. Returns
  410. -------
  411. value : scalar value
  412. """
  413. warnings.warn("get_value is deprecated and will be removed "
  414. "in a future release. Please use "
  415. ".at[] or .iat[] accessors instead", FutureWarning,
  416. stacklevel=2)
  417. return self._get_value(*args, **kwargs)
  418. def _get_value(self, *args, **kwargs):
  419. nargs = len(args)
  420. nreq = self._AXIS_LEN
  421. # require an arg for each axis
  422. if nargs != nreq:
  423. raise TypeError('There must be an argument for each axis, you gave'
  424. ' {0} args, but {1} are required'.format(nargs,
  425. nreq))
  426. takeable = kwargs.pop('takeable', None)
  427. if kwargs:
  428. raise TypeError('get_value() got an unexpected keyword '
  429. 'argument "{0}"'.format(list(kwargs.keys())[0]))
  430. if takeable is True:
  431. lower = self._iget_item_cache(args[0])
  432. else:
  433. lower = self._get_item_cache(args[0])
  434. return lower._get_value(*args[1:], takeable=takeable)
  435. _get_value.__doc__ = get_value.__doc__
  436. def set_value(self, *args, **kwargs):
  437. """
  438. Quickly set single value at (item, major, minor) location.
  439. .. deprecated:: 0.21.0
  440. Please use .at[] or .iat[] accessors.
  441. Parameters
  442. ----------
  443. item : item label (panel item)
  444. major : major axis label (panel item row)
  445. minor : minor axis label (panel item column)
  446. value : scalar
  447. takeable : interpret the passed labels as indexers, default False
  448. Returns
  449. -------
  450. panel : Panel
  451. If label combo is contained, will be reference to calling Panel,
  452. otherwise a new object
  453. """
  454. warnings.warn("set_value is deprecated and will be removed "
  455. "in a future release. Please use "
  456. ".at[] or .iat[] accessors instead", FutureWarning,
  457. stacklevel=2)
  458. return self._set_value(*args, **kwargs)
  459. def _set_value(self, *args, **kwargs):
  460. # require an arg for each axis and the value
  461. nargs = len(args)
  462. nreq = self._AXIS_LEN + 1
  463. if nargs != nreq:
  464. raise TypeError('There must be an argument for each axis plus the '
  465. 'value provided, you gave {0} args, but {1} are '
  466. 'required'.format(nargs, nreq))
  467. takeable = kwargs.pop('takeable', None)
  468. if kwargs:
  469. raise TypeError('set_value() got an unexpected keyword '
  470. 'argument "{0}"'.format(list(kwargs.keys())[0]))
  471. try:
  472. if takeable is True:
  473. lower = self._iget_item_cache(args[0])
  474. else:
  475. lower = self._get_item_cache(args[0])
  476. lower._set_value(*args[1:], takeable=takeable)
  477. return self
  478. except KeyError:
  479. axes = self._expand_axes(args)
  480. d = self._construct_axes_dict_from(self, axes, copy=False)
  481. result = self.reindex(**d)
  482. args = list(args)
  483. likely_dtype, args[-1] = infer_dtype_from_scalar(args[-1])
  484. made_bigger = not np.array_equal(axes[0], self._info_axis)
  485. # how to make this logic simpler?
  486. if made_bigger:
  487. maybe_cast_item(result, args[0], likely_dtype)
  488. return result._set_value(*args)
  489. _set_value.__doc__ = set_value.__doc__
  490. def _box_item_values(self, key, values):
  491. if self.ndim == values.ndim:
  492. result = self._constructor(values)
  493. # a dup selection will yield a full ndim
  494. if result._get_axis(0).is_unique:
  495. result = result[key]
  496. return result
  497. d = self._construct_axes_dict_for_slice(self._AXIS_ORDERS[1:])
  498. return self._constructor_sliced(values, **d)
  499. def __setitem__(self, key, value):
  500. key = com.apply_if_callable(key, self)
  501. shape = tuple(self.shape)
  502. if isinstance(value, self._constructor_sliced):
  503. value = value.reindex(
  504. **self._construct_axes_dict_for_slice(self._AXIS_ORDERS[1:]))
  505. mat = value.values
  506. elif isinstance(value, np.ndarray):
  507. if value.shape != shape[1:]:
  508. raise ValueError('shape of value must be {0}, shape of given '
  509. 'object was {1}'.format(
  510. shape[1:], tuple(map(int, value.shape))))
  511. mat = np.asarray(value)
  512. elif is_scalar(value):
  513. mat = cast_scalar_to_array(shape[1:], value)
  514. else:
  515. raise TypeError('Cannot set item of '
  516. 'type: {dtype!s}'.format(dtype=type(value)))
  517. mat = mat.reshape(tuple([1]) + shape[1:])
  518. NDFrame._set_item(self, key, mat)
  519. def _unpickle_panel_compat(self, state): # pragma: no cover
  520. """
  521. Unpickle the panel.
  522. """
  523. from pandas.io.pickle import _unpickle_array
  524. _unpickle = _unpickle_array
  525. vals, items, major, minor = state
  526. items = _unpickle(items)
  527. major = _unpickle(major)
  528. minor = _unpickle(minor)
  529. values = _unpickle(vals)
  530. wp = Panel(values, items, major, minor)
  531. self._data = wp._data
  532. def conform(self, frame, axis='items'):
  533. """
  534. Conform input DataFrame to align with chosen axis pair.
  535. Parameters
  536. ----------
  537. frame : DataFrame
  538. axis : {'items', 'major', 'minor'}
  539. Axis the input corresponds to. E.g., if axis='major', then
  540. the frame's columns would be items, and the index would be
  541. values of the minor axis
  542. Returns
  543. -------
  544. DataFrame
  545. """
  546. axes = self._get_plane_axes(axis)
  547. return frame.reindex(**self._extract_axes_for_slice(self, axes))
  548. def head(self, n=5):
  549. raise NotImplementedError
  550. def tail(self, n=5):
  551. raise NotImplementedError
  552. def round(self, decimals=0, *args, **kwargs):
  553. """
  554. Round each value in Panel to a specified number of decimal places.
  555. .. versionadded:: 0.18.0
  556. Parameters
  557. ----------
  558. decimals : int
  559. Number of decimal places to round to (default: 0).
  560. If decimals is negative, it specifies the number of
  561. positions to the left of the decimal point.
  562. Returns
  563. -------
  564. Panel object
  565. See Also
  566. --------
  567. numpy.around
  568. """
  569. nv.validate_round(args, kwargs)
  570. if is_integer(decimals):
  571. result = np.apply_along_axis(np.round, 0, self.values)
  572. return self._wrap_result(result, axis=0)
  573. raise TypeError("decimals must be an integer")
  574. def _needs_reindex_multi(self, axes, method, level):
  575. """
  576. Don't allow a multi reindex on Panel or above ndim.
  577. """
  578. return False
  579. def align(self, other, **kwargs):
  580. raise NotImplementedError
  581. def dropna(self, axis=0, how='any', inplace=False):
  582. """
  583. Drop 2D from panel, holding passed axis constant.
  584. Parameters
  585. ----------
  586. axis : int, default 0
  587. Axis to hold constant. E.g. axis=1 will drop major_axis entries
  588. having a certain amount of NA data
  589. how : {'all', 'any'}, default 'any'
  590. 'any': one or more values are NA in the DataFrame along the
  591. axis. For 'all' they all must be.
  592. inplace : bool, default False
  593. If True, do operation inplace and return None.
  594. Returns
  595. -------
  596. dropped : Panel
  597. """
  598. axis = self._get_axis_number(axis)
  599. values = self.values
  600. mask = notna(values)
  601. for ax in reversed(sorted(set(range(self._AXIS_LEN)) - {axis})):
  602. mask = mask.sum(ax)
  603. per_slice = np.prod(values.shape[:axis] + values.shape[axis + 1:])
  604. if how == 'all':
  605. cond = mask > 0
  606. else:
  607. cond = mask == per_slice
  608. new_ax = self._get_axis(axis)[cond]
  609. result = self.reindex_axis(new_ax, axis=axis)
  610. if inplace:
  611. self._update_inplace(result)
  612. else:
  613. return result
  614. def _combine(self, other, func, axis=0):
  615. if isinstance(other, Panel):
  616. return self._combine_panel(other, func)
  617. elif isinstance(other, DataFrame):
  618. return self._combine_frame(other, func, axis=axis)
  619. elif is_scalar(other):
  620. return self._combine_const(other, func)
  621. else:
  622. raise NotImplementedError(
  623. "{otype!s} is not supported in combine operation with "
  624. "{selftype!s}".format(otype=type(other), selftype=type(self)))
  625. def _combine_const(self, other, func):
  626. with np.errstate(all='ignore'):
  627. new_values = func(self.values, other)
  628. d = self._construct_axes_dict()
  629. return self._constructor(new_values, **d)
  630. def _combine_frame(self, other, func, axis=0):
  631. index, columns = self._get_plane_axes(axis)
  632. axis = self._get_axis_number(axis)
  633. other = other.reindex(index=index, columns=columns)
  634. with np.errstate(all='ignore'):
  635. if axis == 0:
  636. new_values = func(self.values, other.values)
  637. elif axis == 1:
  638. new_values = func(self.values.swapaxes(0, 1), other.values.T)
  639. new_values = new_values.swapaxes(0, 1)
  640. elif axis == 2:
  641. new_values = func(self.values.swapaxes(0, 2), other.values)
  642. new_values = new_values.swapaxes(0, 2)
  643. return self._constructor(new_values, self.items, self.major_axis,
  644. self.minor_axis)
  645. def _combine_panel(self, other, func):
  646. items = self.items.union(other.items)
  647. major = self.major_axis.union(other.major_axis)
  648. minor = self.minor_axis.union(other.minor_axis)
  649. # could check that everything's the same size, but forget it
  650. this = self.reindex(items=items, major=major, minor=minor)
  651. other = other.reindex(items=items, major=major, minor=minor)
  652. with np.errstate(all='ignore'):
  653. result_values = func(this.values, other.values)
  654. return self._constructor(result_values, items, major, minor)
  655. def major_xs(self, key):
  656. """
  657. Return slice of panel along major axis.
  658. Parameters
  659. ----------
  660. key : object
  661. Major axis label
  662. Returns
  663. -------
  664. y : DataFrame
  665. index -> minor axis, columns -> items
  666. Notes
  667. -----
  668. major_xs is only for getting, not setting values.
  669. MultiIndex Slicers is a generic way to get/set values on any level or
  670. levels and is a superset of major_xs functionality, see
  671. :ref:`MultiIndex Slicers <advanced.mi_slicers>`
  672. """
  673. return self.xs(key, axis=self._AXIS_LEN - 2)
  674. def minor_xs(self, key):
  675. """
  676. Return slice of panel along minor axis.
  677. Parameters
  678. ----------
  679. key : object
  680. Minor axis label
  681. Returns
  682. -------
  683. y : DataFrame
  684. index -> major axis, columns -> items
  685. Notes
  686. -----
  687. minor_xs is only for getting, not setting values.
  688. MultiIndex Slicers is a generic way to get/set values on any level or
  689. levels and is a superset of minor_xs functionality, see
  690. :ref:`MultiIndex Slicers <advanced.mi_slicers>`
  691. """
  692. return self.xs(key, axis=self._AXIS_LEN - 1)
  693. def xs(self, key, axis=1):
  694. """
  695. Return slice of panel along selected axis.
  696. Parameters
  697. ----------
  698. key : object
  699. Label
  700. axis : {'items', 'major', 'minor}, default 1/'major'
  701. Returns
  702. -------
  703. y : ndim(self)-1
  704. Notes
  705. -----
  706. xs is only for getting, not setting values.
  707. MultiIndex Slicers is a generic way to get/set values on any level or
  708. levels and is a superset of xs functionality, see
  709. :ref:`MultiIndex Slicers <advanced.mi_slicers>`
  710. """
  711. axis = self._get_axis_number(axis)
  712. if axis == 0:
  713. return self[key]
  714. self._consolidate_inplace()
  715. axis_number = self._get_axis_number(axis)
  716. new_data = self._data.xs(key, axis=axis_number, copy=False)
  717. result = self._construct_return_type(new_data)
  718. copy = new_data.is_mixed_type
  719. result._set_is_copy(self, copy=copy)
  720. return result
  721. _xs = xs
  722. def _ixs(self, i, axis=0):
  723. """
  724. Parameters
  725. ----------
  726. i : int, slice, or sequence of integers
  727. axis : int
  728. """
  729. ax = self._get_axis(axis)
  730. key = ax[i]
  731. # xs cannot handle a non-scalar key, so just reindex here
  732. # if we have a multi-index and a single tuple, then its a reduction
  733. # (GH 7516)
  734. if not (isinstance(ax, MultiIndex) and isinstance(key, tuple)):
  735. if is_list_like(key):
  736. indexer = {self._get_axis_name(axis): key}
  737. return self.reindex(**indexer)
  738. # a reduction
  739. if axis == 0:
  740. values = self._data.iget(i)
  741. return self._box_item_values(key, values)
  742. # xs by position
  743. self._consolidate_inplace()
  744. new_data = self._data.xs(i, axis=axis, copy=True, takeable=True)
  745. return self._construct_return_type(new_data)
  746. def groupby(self, function, axis='major'):
  747. """
  748. Group data on given axis, returning GroupBy object.
  749. Parameters
  750. ----------
  751. function : callable
  752. Mapping function for chosen access
  753. axis : {'major', 'minor', 'items'}, default 'major'
  754. Returns
  755. -------
  756. grouped : PanelGroupBy
  757. """
  758. from pandas.core.groupby import PanelGroupBy
  759. axis = self._get_axis_number(axis)
  760. return PanelGroupBy(self, function, axis=axis)
  761. def to_frame(self, filter_observations=True):
  762. """
  763. Transform wide format into long (stacked) format as DataFrame whose
  764. columns are the Panel's items and whose index is a MultiIndex formed
  765. of the Panel's major and minor axes.
  766. Parameters
  767. ----------
  768. filter_observations : boolean, default True
  769. Drop (major, minor) pairs without a complete set of observations
  770. across all the items
  771. Returns
  772. -------
  773. y : DataFrame
  774. """
  775. _, N, K = self.shape
  776. if filter_observations:
  777. # shaped like the return DataFrame
  778. mask = notna(self.values).all(axis=0)
  779. # size = mask.sum()
  780. selector = mask.ravel()
  781. else:
  782. # size = N * K
  783. selector = slice(None, None)
  784. data = {item: self[item].values.ravel()[selector]
  785. for item in self.items}
  786. def construct_multi_parts(idx, n_repeat, n_shuffle=1):
  787. # Replicates and shuffles MultiIndex, returns individual attributes
  788. codes = [np.repeat(x, n_repeat) for x in idx.codes]
  789. # Assumes that each label is divisible by n_shuffle
  790. codes = [x.reshape(n_shuffle, -1).ravel(order='F')
  791. for x in codes]
  792. codes = [x[selector] for x in codes]
  793. levels = idx.levels
  794. names = idx.names
  795. return codes, levels, names
  796. def construct_index_parts(idx, major=True):
  797. levels = [idx]
  798. if major:
  799. codes = [np.arange(N).repeat(K)[selector]]
  800. names = idx.name or 'major'
  801. else:
  802. codes = np.arange(K).reshape(1, K)[np.zeros(N, dtype=int)]
  803. codes = [codes.ravel()[selector]]
  804. names = idx.name or 'minor'
  805. names = [names]
  806. return codes, levels, names
  807. if isinstance(self.major_axis, MultiIndex):
  808. major_codes, major_levels, major_names = construct_multi_parts(
  809. self.major_axis, n_repeat=K)
  810. else:
  811. major_codes, major_levels, major_names = construct_index_parts(
  812. self.major_axis)
  813. if isinstance(self.minor_axis, MultiIndex):
  814. minor_codes, minor_levels, minor_names = construct_multi_parts(
  815. self.minor_axis, n_repeat=N, n_shuffle=K)
  816. else:
  817. minor_codes, minor_levels, minor_names = construct_index_parts(
  818. self.minor_axis, major=False)
  819. levels = major_levels + minor_levels
  820. codes = major_codes + minor_codes
  821. names = major_names + minor_names
  822. index = MultiIndex(levels=levels, codes=codes, names=names,
  823. verify_integrity=False)
  824. return DataFrame(data, index=index, columns=self.items)
  825. def apply(self, func, axis='major', **kwargs):
  826. """
  827. Applies function along axis (or axes) of the Panel.
  828. Parameters
  829. ----------
  830. func : function
  831. Function to apply to each combination of 'other' axes
  832. e.g. if axis = 'items', the combination of major_axis/minor_axis
  833. will each be passed as a Series; if axis = ('items', 'major'),
  834. DataFrames of items & major axis will be passed
  835. axis : {'items', 'minor', 'major'}, or {0, 1, 2}, or a tuple with two
  836. axes
  837. Additional keyword arguments will be passed as keywords to the function
  838. Returns
  839. -------
  840. result : Panel, DataFrame, or Series
  841. Examples
  842. --------
  843. Returns a Panel with the square root of each element
  844. >>> p = pd.Panel(np.random.rand(4, 3, 2)) # doctest: +SKIP
  845. >>> p.apply(np.sqrt)
  846. Equivalent to p.sum(1), returning a DataFrame
  847. >>> p.apply(lambda x: x.sum(), axis=1) # doctest: +SKIP
  848. Equivalent to previous:
  849. >>> p.apply(lambda x: x.sum(), axis='major') # doctest: +SKIP
  850. Return the shapes of each DataFrame over axis 2 (i.e the shapes of
  851. items x major), as a Series
  852. >>> p.apply(lambda x: x.shape, axis=(0,1)) # doctest: +SKIP
  853. """
  854. if kwargs and not isinstance(func, np.ufunc):
  855. f = lambda x: func(x, **kwargs)
  856. else:
  857. f = func
  858. # 2d-slabs
  859. if isinstance(axis, (tuple, list)) and len(axis) == 2:
  860. return self._apply_2d(f, axis=axis)
  861. axis = self._get_axis_number(axis)
  862. # try ufunc like
  863. if isinstance(f, np.ufunc):
  864. try:
  865. with np.errstate(all='ignore'):
  866. result = np.apply_along_axis(func, axis, self.values)
  867. return self._wrap_result(result, axis=axis)
  868. except (AttributeError):
  869. pass
  870. # 1d
  871. return self._apply_1d(f, axis=axis)
  872. def _apply_1d(self, func, axis):
  873. axis_name = self._get_axis_name(axis)
  874. ndim = self.ndim
  875. values = self.values
  876. # iter thru the axes
  877. slice_axis = self._get_axis(axis)
  878. slice_indexer = [0] * (ndim - 1)
  879. indexer = np.zeros(ndim, 'O')
  880. indlist = list(range(ndim))
  881. indlist.remove(axis)
  882. indexer[axis] = slice(None, None)
  883. indexer.put(indlist, slice_indexer)
  884. planes = [self._get_axis(axi) for axi in indlist]
  885. shape = np.array(self.shape).take(indlist)
  886. # all the iteration points
  887. points = cartesian_product(planes)
  888. results = []
  889. for i in range(np.prod(shape)):
  890. # construct the object
  891. pts = tuple(p[i] for p in points)
  892. indexer.put(indlist, slice_indexer)
  893. obj = Series(values[tuple(indexer)], index=slice_axis, name=pts)
  894. result = func(obj)
  895. results.append(result)
  896. # increment the indexer
  897. slice_indexer[-1] += 1
  898. n = -1
  899. while (slice_indexer[n] >= shape[n]) and (n > (1 - ndim)):
  900. slice_indexer[n - 1] += 1
  901. slice_indexer[n] = 0
  902. n -= 1
  903. # empty object
  904. if not len(results):
  905. return self._constructor(**self._construct_axes_dict())
  906. # same ndim as current
  907. if isinstance(results[0], Series):
  908. arr = np.vstack([r.values for r in results])
  909. arr = arr.T.reshape(tuple([len(slice_axis)] + list(shape)))
  910. tranp = np.array([axis] + indlist).argsort()
  911. arr = arr.transpose(tuple(list(tranp)))
  912. return self._constructor(arr, **self._construct_axes_dict())
  913. # ndim-1 shape
  914. results = np.array(results).reshape(shape)
  915. if results.ndim == 2 and axis_name != self._info_axis_name:
  916. results = results.T
  917. planes = planes[::-1]
  918. return self._construct_return_type(results, planes)
  919. def _apply_2d(self, func, axis):
  920. """
  921. Handle 2-d slices, equiv to iterating over the other axis.
  922. """
  923. ndim = self.ndim
  924. axis = [self._get_axis_number(a) for a in axis]
  925. # construct slabs, in 2-d this is a DataFrame result
  926. indexer_axis = list(range(ndim))
  927. for a in axis:
  928. indexer_axis.remove(a)
  929. indexer_axis = indexer_axis[0]
  930. slicer = [slice(None, None)] * ndim
  931. ax = self._get_axis(indexer_axis)
  932. results = []
  933. for i, e in enumerate(ax):
  934. slicer[indexer_axis] = i
  935. sliced = self.iloc[tuple(slicer)]
  936. obj = func(sliced)
  937. results.append((e, obj))
  938. return self._construct_return_type(dict(results))
  939. def _reduce(self, op, name, axis=0, skipna=True, numeric_only=None,
  940. filter_type=None, **kwds):
  941. if numeric_only:
  942. raise NotImplementedError('Panel.{0} does not implement '
  943. 'numeric_only.'.format(name))
  944. if axis is None and filter_type == 'bool':
  945. # labels = None
  946. # constructor = None
  947. axis_number = None
  948. axis_name = None
  949. else:
  950. # TODO: Make other agg func handle axis=None properly
  951. axis = self._get_axis_number(axis)
  952. # labels = self._get_agg_axis(axis)
  953. # constructor = self._constructor
  954. axis_name = self._get_axis_name(axis)
  955. axis_number = self._get_axis_number(axis_name)
  956. f = lambda x: op(x, axis=axis_number, skipna=skipna, **kwds)
  957. with np.errstate(all='ignore'):
  958. result = f(self.values)
  959. if axis is None and filter_type == 'bool':
  960. return np.bool_(result)
  961. axes = self._get_plane_axes(axis_name)
  962. if result.ndim == 2 and axis_name != self._info_axis_name:
  963. result = result.T
  964. return self._construct_return_type(result, axes)
  965. def _construct_return_type(self, result, axes=None):
  966. """
  967. Return the type for the ndim of the result.
  968. """
  969. ndim = getattr(result, 'ndim', None)
  970. # need to assume they are the same
  971. if ndim is None:
  972. if isinstance(result, dict):
  973. ndim = getattr(list(compat.itervalues(result))[0], 'ndim', 0)
  974. # have a dict, so top-level is +1 dim
  975. if ndim != 0:
  976. ndim += 1
  977. # scalar
  978. if ndim == 0:
  979. return Series(result)
  980. # same as self
  981. elif self.ndim == ndim:
  982. # return the construction dictionary for these axes
  983. if axes is None:
  984. return self._constructor(result)
  985. return self._constructor(result, **self._construct_axes_dict())
  986. # sliced
  987. elif self.ndim == ndim + 1:
  988. if axes is None:
  989. return self._constructor_sliced(result)
  990. return self._constructor_sliced(
  991. result, **self._extract_axes_for_slice(self, axes))
  992. raise ValueError('invalid _construct_return_type [self->{self}] '
  993. '[result->{result}]'.format(self=self, result=result))
  994. def _wrap_result(self, result, axis):
  995. axis = self._get_axis_name(axis)
  996. axes = self._get_plane_axes(axis)
  997. if result.ndim == 2 and axis != self._info_axis_name:
  998. result = result.T
  999. return self._construct_return_type(result, axes)
  1000. @Substitution(**_shared_doc_kwargs)
  1001. @Appender(NDFrame.reindex.__doc__)
  1002. def reindex(self, *args, **kwargs):
  1003. major = kwargs.pop("major", None)
  1004. minor = kwargs.pop('minor', None)
  1005. if major is not None:
  1006. if kwargs.get("major_axis"):
  1007. raise TypeError("Cannot specify both 'major' and 'major_axis'")
  1008. kwargs['major_axis'] = major
  1009. if minor is not None:
  1010. if kwargs.get("minor_axis"):
  1011. raise TypeError("Cannot specify both 'minor' and 'minor_axis'")
  1012. kwargs['minor_axis'] = minor
  1013. axes = validate_axis_style_args(self, args, kwargs, 'labels',
  1014. 'reindex')
  1015. kwargs.update(axes)
  1016. kwargs.pop('axis', None)
  1017. kwargs.pop('labels', None)
  1018. with warnings.catch_warnings():
  1019. warnings.simplefilter("ignore", FutureWarning)
  1020. # do not warn about constructing Panel when reindexing
  1021. result = super(Panel, self).reindex(**kwargs)
  1022. return result
  1023. @Substitution(**_shared_doc_kwargs)
  1024. @Appender(NDFrame.rename.__doc__)
  1025. def rename(self, items=None, major_axis=None, minor_axis=None, **kwargs):
  1026. major_axis = (major_axis if major_axis is not None else
  1027. kwargs.pop('major', None))
  1028. minor_axis = (minor_axis if minor_axis is not None else
  1029. kwargs.pop('minor', None))
  1030. return super(Panel, self).rename(items=items, major_axis=major_axis,
  1031. minor_axis=minor_axis, **kwargs)
  1032. @Appender(_shared_docs['reindex_axis'] % _shared_doc_kwargs)
  1033. def reindex_axis(self, labels, axis=0, method=None, level=None, copy=True,
  1034. limit=None, fill_value=np.nan):
  1035. return super(Panel, self).reindex_axis(labels=labels, axis=axis,
  1036. method=method, level=level,
  1037. copy=copy, limit=limit,
  1038. fill_value=fill_value)
  1039. @Substitution(**_shared_doc_kwargs)
  1040. @Appender(NDFrame.transpose.__doc__)
  1041. def transpose(self, *args, **kwargs):
  1042. # check if a list of axes was passed in instead as a
  1043. # single *args element
  1044. if (len(args) == 1 and hasattr(args[0], '__iter__') and
  1045. not is_string_like(args[0])):
  1046. axes = args[0]
  1047. else:
  1048. axes = args
  1049. if 'axes' in kwargs and axes:
  1050. raise TypeError("transpose() got multiple values for "
  1051. "keyword argument 'axes'")
  1052. elif not axes:
  1053. axes = kwargs.pop('axes', ())
  1054. return super(Panel, self).transpose(*axes, **kwargs)
  1055. @Substitution(**_shared_doc_kwargs)
  1056. @Appender(NDFrame.fillna.__doc__)
  1057. def fillna(self, value=None, method=None, axis=None, inplace=False,
  1058. limit=None, downcast=None, **kwargs):
  1059. return super(Panel, self).fillna(value=value, method=method, axis=axis,
  1060. inplace=inplace, limit=limit,
  1061. downcast=downcast, **kwargs)
  1062. def count(self, axis='major'):
  1063. """
  1064. Return number of observations over requested axis.
  1065. Parameters
  1066. ----------
  1067. axis : {'items', 'major', 'minor'} or {0, 1, 2}
  1068. Returns
  1069. -------
  1070. count : DataFrame
  1071. """
  1072. i = self._get_axis_number(axis)
  1073. values = self.values
  1074. mask = np.isfinite(values)
  1075. result = mask.sum(axis=i, dtype='int64')
  1076. return self._wrap_result(result, axis)
  1077. def shift(self, periods=1, freq=None, axis='major'):
  1078. """
  1079. Shift index by desired number of periods with an optional time freq.
  1080. The shifted data will not include the dropped periods and the
  1081. shifted axis will be smaller than the original. This is different
  1082. from the behavior of DataFrame.shift()
  1083. Parameters
  1084. ----------
  1085. periods : int
  1086. Number of periods to move, can be positive or negative
  1087. freq : DateOffset, timedelta, or time rule string, optional
  1088. axis : {'items', 'major', 'minor'} or {0, 1, 2}
  1089. Returns
  1090. -------
  1091. shifted : Panel
  1092. """
  1093. if freq:
  1094. return self.tshift(periods, freq, axis=axis)
  1095. return super(Panel, self).slice_shift(periods, axis=axis)
  1096. def tshift(self, periods=1, freq=None, axis='major'):
  1097. return super(Panel, self).tshift(periods, freq, axis)
  1098. def join(self, other, how='left', lsuffix='', rsuffix=''):
  1099. """
  1100. Join items with other Panel either on major and minor axes column.
  1101. Parameters
  1102. ----------
  1103. other : Panel or list of Panels
  1104. Index should be similar to one of the columns in this one
  1105. how : {'left', 'right', 'outer', 'inner'}
  1106. How to handle indexes of the two objects. Default: 'left'
  1107. for joining on index, None otherwise
  1108. * left: use calling frame's index
  1109. * right: use input frame's index
  1110. * outer: form union of indexes
  1111. * inner: use intersection of indexes
  1112. lsuffix : string
  1113. Suffix to use from left frame's overlapping columns
  1114. rsuffix : string
  1115. Suffix to use from right frame's overlapping columns
  1116. Returns
  1117. -------
  1118. joined : Panel
  1119. """
  1120. from pandas.core.reshape.concat import concat
  1121. if isinstance(other, Panel):
  1122. join_major, join_minor = self._get_join_index(other, how)
  1123. this = self.reindex(major=join_major, minor=join_minor)
  1124. other = other.reindex(major=join_major, minor=join_minor)
  1125. merged_data = this._data.merge(other._data, lsuffix, rsuffix)
  1126. return self._constructor(merged_data)
  1127. else:
  1128. if lsuffix or rsuffix:
  1129. raise ValueError('Suffixes not supported when passing '
  1130. 'multiple panels')
  1131. if how == 'left':
  1132. how = 'outer'
  1133. join_axes = [self.major_axis, self.minor_axis]
  1134. elif how == 'right':
  1135. raise ValueError('Right join not supported with multiple '
  1136. 'panels')
  1137. else:
  1138. join_axes = None
  1139. return concat([self] + list(other), axis=0, join=how,
  1140. join_axes=join_axes, verify_integrity=True)
  1141. @deprecate_kwarg(old_arg_name='raise_conflict', new_arg_name='errors',
  1142. mapping={False: 'ignore', True: 'raise'})
  1143. def update(self, other, join='left', overwrite=True, filter_func=None,
  1144. errors='ignore'):
  1145. """
  1146. Modify Panel in place using non-NA values from other Panel.
  1147. May also use object coercible to Panel. Will align on items.
  1148. Parameters
  1149. ----------
  1150. other : Panel, or object coercible to Panel
  1151. The object from which the caller will be udpated.
  1152. join : {'left', 'right', 'outer', 'inner'}, default 'left'
  1153. How individual DataFrames are joined.
  1154. overwrite : bool, default True
  1155. If True then overwrite values for common keys in the calling Panel.
  1156. filter_func : callable(1d-array) -> 1d-array<bool>, default None
  1157. Can choose to replace values other than NA. Return True for values
  1158. that should be updated.
  1159. errors : {'raise', 'ignore'}, default 'ignore'
  1160. If 'raise', will raise an error if a DataFrame and other both.
  1161. .. versionchanged :: 0.24.0
  1162. Changed from `raise_conflict=False|True`
  1163. to `errors='ignore'|'raise'`.
  1164. See Also
  1165. --------
  1166. DataFrame.update : Similar method for DataFrames.
  1167. dict.update : Similar method for dictionaries.
  1168. """
  1169. if not isinstance(other, self._constructor):
  1170. other = self._constructor(other)
  1171. axis_name = self._info_axis_name
  1172. axis_values = self._info_axis
  1173. other = other.reindex(**{axis_name: axis_values})
  1174. for frame in axis_values:
  1175. self[frame].update(other[frame], join=join, overwrite=overwrite,
  1176. filter_func=filter_func, errors=errors)
  1177. def _get_join_index(self, other, how):
  1178. if how == 'left':
  1179. join_major, join_minor = self.major_axis, self.minor_axis
  1180. elif how == 'right':
  1181. join_major, join_minor = other.major_axis, other.minor_axis
  1182. elif how == 'inner':
  1183. join_major = self.major_axis.intersection(other.major_axis)
  1184. join_minor = self.minor_axis.intersection(other.minor_axis)
  1185. elif how == 'outer':
  1186. join_major = self.major_axis.union(other.major_axis)
  1187. join_minor = self.minor_axis.union(other.minor_axis)
  1188. return join_major, join_minor
  1189. # miscellaneous data creation
  1190. @staticmethod
  1191. def _extract_axes(self, data, axes, **kwargs):
  1192. """
  1193. Return a list of the axis indices.
  1194. """
  1195. return [self._extract_axis(self, data, axis=i, **kwargs)
  1196. for i, a in enumerate(axes)]
  1197. @staticmethod
  1198. def _extract_axes_for_slice(self, axes):
  1199. """
  1200. Return the slice dictionary for these axes.
  1201. """
  1202. return {self._AXIS_SLICEMAP[i]: a for i, a in
  1203. zip(self._AXIS_ORDERS[self._AXIS_LEN - len(axes):], axes)}
  1204. @staticmethod
  1205. def _prep_ndarray(self, values, copy=True):
  1206. if not isinstance(values, np.ndarray):
  1207. values = np.asarray(values)
  1208. # NumPy strings are a pain, convert to object
  1209. if issubclass(values.dtype.type, compat.string_types):
  1210. values = np.array(values, dtype=object, copy=True)
  1211. else:
  1212. if copy:
  1213. values = values.copy()
  1214. if values.ndim != self._AXIS_LEN:
  1215. raise ValueError("The number of dimensions required is {0}, "
  1216. "but the number of dimensions of the "
  1217. "ndarray given was {1}".format(self._AXIS_LEN,
  1218. values.ndim))
  1219. return values
  1220. @staticmethod
  1221. def _homogenize_dict(self, frames, intersect=True, dtype=None):
  1222. """
  1223. Conform set of _constructor_sliced-like objects to either
  1224. an intersection of indices / columns or a union.
  1225. Parameters
  1226. ----------
  1227. frames : dict
  1228. intersect : boolean, default True
  1229. Returns
  1230. -------
  1231. dict of aligned results & indices
  1232. """
  1233. result = dict()
  1234. # caller differs dict/ODict, preserved type
  1235. if isinstance(frames, OrderedDict):
  1236. result = OrderedDict()
  1237. adj_frames = OrderedDict()
  1238. for k, v in compat.iteritems(frames):
  1239. if isinstance(v, dict):
  1240. adj_frames[k] = self._constructor_sliced(v)
  1241. else:
  1242. adj_frames[k] = v
  1243. axes = self._AXIS_ORDERS[1:]
  1244. axes_dict = {a: ax for a, ax in zip(axes, self._extract_axes(
  1245. self, adj_frames, axes, intersect=intersect))}
  1246. reindex_dict = {self._AXIS_SLICEMAP[a]: axes_dict[a] for a in axes}
  1247. reindex_dict['copy'] = False
  1248. for key, frame in compat.iteritems(adj_frames):
  1249. if frame is not None:
  1250. result[key] = frame.reindex(**reindex_dict)
  1251. else:
  1252. result[key] = None
  1253. axes_dict['data'] = result
  1254. axes_dict['dtype'] = dtype
  1255. return axes_dict
  1256. @staticmethod
  1257. def _extract_axis(self, data, axis=0, intersect=False):
  1258. index = None
  1259. if len(data) == 0:
  1260. index = Index([])
  1261. elif len(data) > 0:
  1262. raw_lengths = []
  1263. have_raw_arrays = False
  1264. have_frames = False
  1265. for v in data.values():
  1266. if isinstance(v, self._constructor_sliced):
  1267. have_frames = True
  1268. elif v is not None:
  1269. have_raw_arrays = True
  1270. raw_lengths.append(v.shape[axis])
  1271. if have_frames:
  1272. # we want the "old" behavior here, of sorting only
  1273. # 1. we're doing a union (intersect=False)
  1274. # 2. the indices are not aligned.
  1275. index = _get_objs_combined_axis(data.values(), axis=axis,
  1276. intersect=intersect, sort=None)
  1277. if have_raw_arrays:
  1278. lengths = list(set(raw_lengths))
  1279. if len(lengths) > 1:
  1280. raise ValueError('ndarrays must match shape on '
  1281. 'axis {ax}'.format(ax=axis))
  1282. if have_frames:
  1283. if lengths[0] != len(index):
  1284. raise AssertionError('Length of data and index must match')
  1285. else:
  1286. index = Index(np.arange(lengths[0]))
  1287. if index is None:
  1288. index = Index([])
  1289. return ensure_index(index)
  1290. def sort_values(self, *args, **kwargs):
  1291. """
  1292. NOT IMPLEMENTED: do not call this method, as sorting values is not
  1293. supported for Panel objects and will raise an error.
  1294. """
  1295. super(Panel, self).sort_values(*args, **kwargs)
  1296. Panel._setup_axes(axes=['items', 'major_axis', 'minor_axis'], info_axis=0,
  1297. stat_axis=1, aliases={'major': 'major_axis',
  1298. 'minor': 'minor_axis'},
  1299. slicers={'major_axis': 'index',
  1300. 'minor_axis': 'columns'},
  1301. docs={})
  1302. ops.add_special_arithmetic_methods(Panel)
  1303. ops.add_flex_arithmetic_methods(Panel)
  1304. Panel._add_numeric_operations()