12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489 |
- from typing import Hashable, List, Tuple, Union
- import numpy as np
- from pandas._libs.indexing import _NDFrameIndexerBase
- from pandas._libs.lib import item_from_zerodim
- from pandas.errors import AbstractMethodError
- from pandas.util._decorators import Appender
- from pandas.core.dtypes.common import (
- is_float,
- is_integer,
- is_iterator,
- is_list_like,
- is_numeric_dtype,
- is_object_dtype,
- is_scalar,
- is_sequence,
- )
- from pandas.core.dtypes.concat import concat_compat
- from pandas.core.dtypes.generic import ABCDataFrame, ABCMultiIndex, ABCSeries
- from pandas.core.dtypes.missing import _infer_fill_value, isna
- import pandas.core.common as com
- from pandas.core.indexers import (
- check_array_indexer,
- is_list_like_indexer,
- length_of_indexer,
- )
- from pandas.core.indexes.api import Index, InvalidIndexError
- # "null slice"
- _NS = slice(None, None)
- # the public IndexSlicerMaker
- class _IndexSlice:
- """
- Create an object to more easily perform multi-index slicing.
- See Also
- --------
- MultiIndex.remove_unused_levels : New MultiIndex with no unused levels.
- Notes
- -----
- See :ref:`Defined Levels <advanced.shown_levels>`
- for further info on slicing a MultiIndex.
- Examples
- --------
- >>> midx = pd.MultiIndex.from_product([['A0','A1'], ['B0','B1','B2','B3']])
- >>> columns = ['foo', 'bar']
- >>> dfmi = pd.DataFrame(np.arange(16).reshape((len(midx), len(columns))),
- index=midx, columns=columns)
- Using the default slice command:
- >>> dfmi.loc[(slice(None), slice('B0', 'B1')), :]
- foo bar
- A0 B0 0 1
- B1 2 3
- A1 B0 8 9
- B1 10 11
- Using the IndexSlice class for a more intuitive command:
- >>> idx = pd.IndexSlice
- >>> dfmi.loc[idx[:, 'B0':'B1'], :]
- foo bar
- A0 B0 0 1
- B1 2 3
- A1 B0 8 9
- B1 10 11
- """
- def __getitem__(self, arg):
- return arg
- IndexSlice = _IndexSlice()
- class IndexingError(Exception):
- pass
- class IndexingMixin:
- """Mixin for adding .loc/.iloc/.at/.iat to Datafames and Series.
- """
- @property
- def iloc(self) -> "_iLocIndexer":
- """
- Purely integer-location based indexing for selection by position.
- ``.iloc[]`` is primarily integer position based (from ``0`` to
- ``length-1`` of the axis), but may also be used with a boolean
- array.
- Allowed inputs are:
- - An integer, e.g. ``5``.
- - A list or array of integers, e.g. ``[4, 3, 0]``.
- - A slice object with ints, e.g. ``1:7``.
- - A boolean array.
- - A ``callable`` function with one argument (the calling Series or
- DataFrame) and that returns valid output for indexing (one of the above).
- This is useful in method chains, when you don't have a reference to the
- calling object, but would like to base your selection on some value.
- ``.iloc`` will raise ``IndexError`` if a requested indexer is
- out-of-bounds, except *slice* indexers which allow out-of-bounds
- indexing (this conforms with python/numpy *slice* semantics).
- See more at :ref:`Selection by Position <indexing.integer>`.
- See Also
- --------
- DataFrame.iat : Fast integer location scalar accessor.
- DataFrame.loc : Purely label-location based indexer for selection by label.
- Series.iloc : Purely integer-location based indexing for
- selection by position.
- Examples
- --------
- >>> mydict = [{'a': 1, 'b': 2, 'c': 3, 'd': 4},
- ... {'a': 100, 'b': 200, 'c': 300, 'd': 400},
- ... {'a': 1000, 'b': 2000, 'c': 3000, 'd': 4000 }]
- >>> df = pd.DataFrame(mydict)
- >>> df
- a b c d
- 0 1 2 3 4
- 1 100 200 300 400
- 2 1000 2000 3000 4000
- **Indexing just the rows**
- With a scalar integer.
- >>> type(df.iloc[0])
- <class 'pandas.core.series.Series'>
- >>> df.iloc[0]
- a 1
- b 2
- c 3
- d 4
- Name: 0, dtype: int64
- With a list of integers.
- >>> df.iloc[[0]]
- a b c d
- 0 1 2 3 4
- >>> type(df.iloc[[0]])
- <class 'pandas.core.frame.DataFrame'>
- >>> df.iloc[[0, 1]]
- a b c d
- 0 1 2 3 4
- 1 100 200 300 400
- With a `slice` object.
- >>> df.iloc[:3]
- a b c d
- 0 1 2 3 4
- 1 100 200 300 400
- 2 1000 2000 3000 4000
- With a boolean mask the same length as the index.
- >>> df.iloc[[True, False, True]]
- a b c d
- 0 1 2 3 4
- 2 1000 2000 3000 4000
- With a callable, useful in method chains. The `x` passed
- to the ``lambda`` is the DataFrame being sliced. This selects
- the rows whose index label even.
- >>> df.iloc[lambda x: x.index % 2 == 0]
- a b c d
- 0 1 2 3 4
- 2 1000 2000 3000 4000
- **Indexing both axes**
- You can mix the indexer types for the index and columns. Use ``:`` to
- select the entire axis.
- With scalar integers.
- >>> df.iloc[0, 1]
- 2
- With lists of integers.
- >>> df.iloc[[0, 2], [1, 3]]
- b d
- 0 2 4
- 2 2000 4000
- With `slice` objects.
- >>> df.iloc[1:3, 0:3]
- a b c
- 1 100 200 300
- 2 1000 2000 3000
- With a boolean array whose length matches the columns.
- >>> df.iloc[:, [True, False, True, False]]
- a c
- 0 1 3
- 1 100 300
- 2 1000 3000
- With a callable function that expects the Series or DataFrame.
- >>> df.iloc[:, lambda df: [0, 2]]
- a c
- 0 1 3
- 1 100 300
- 2 1000 3000
- """
- return _iLocIndexer("iloc", self)
- @property
- def loc(self) -> "_LocIndexer":
- """
- Access a group of rows and columns by label(s) or a boolean array.
- ``.loc[]`` is primarily label based, but may also be used with a
- boolean array.
- Allowed inputs are:
- - A single label, e.g. ``5`` or ``'a'``, (note that ``5`` is
- interpreted as a *label* of the index, and **never** as an
- integer position along the index).
- - A list or array of labels, e.g. ``['a', 'b', 'c']``.
- - A slice object with labels, e.g. ``'a':'f'``.
- .. warning:: Note that contrary to usual python slices, **both** the
- start and the stop are included
- - A boolean array of the same length as the axis being sliced,
- e.g. ``[True, False, True]``.
- - A ``callable`` function with one argument (the calling Series or
- DataFrame) and that returns valid output for indexing (one of the above)
- See more at :ref:`Selection by Label <indexing.label>`
- Raises
- ------
- KeyError
- If any items are not found.
- See Also
- --------
- DataFrame.at : Access a single value for a row/column label pair.
- DataFrame.iloc : Access group of rows and columns by integer position(s).
- DataFrame.xs : Returns a cross-section (row(s) or column(s)) from the
- Series/DataFrame.
- Series.loc : Access group of values using labels.
- Examples
- --------
- **Getting values**
- >>> df = pd.DataFrame([[1, 2], [4, 5], [7, 8]],
- ... index=['cobra', 'viper', 'sidewinder'],
- ... columns=['max_speed', 'shield'])
- >>> df
- max_speed shield
- cobra 1 2
- viper 4 5
- sidewinder 7 8
- Single label. Note this returns the row as a Series.
- >>> df.loc['viper']
- max_speed 4
- shield 5
- Name: viper, dtype: int64
- List of labels. Note using ``[[]]`` returns a DataFrame.
- >>> df.loc[['viper', 'sidewinder']]
- max_speed shield
- viper 4 5
- sidewinder 7 8
- Single label for row and column
- >>> df.loc['cobra', 'shield']
- 2
- Slice with labels for row and single label for column. As mentioned
- above, note that both the start and stop of the slice are included.
- >>> df.loc['cobra':'viper', 'max_speed']
- cobra 1
- viper 4
- Name: max_speed, dtype: int64
- Boolean list with the same length as the row axis
- >>> df.loc[[False, False, True]]
- max_speed shield
- sidewinder 7 8
- Conditional that returns a boolean Series
- >>> df.loc[df['shield'] > 6]
- max_speed shield
- sidewinder 7 8
- Conditional that returns a boolean Series with column labels specified
- >>> df.loc[df['shield'] > 6, ['max_speed']]
- max_speed
- sidewinder 7
- Callable that returns a boolean Series
- >>> df.loc[lambda df: df['shield'] == 8]
- max_speed shield
- sidewinder 7 8
- **Setting values**
- Set value for all items matching the list of labels
- >>> df.loc[['viper', 'sidewinder'], ['shield']] = 50
- >>> df
- max_speed shield
- cobra 1 2
- viper 4 50
- sidewinder 7 50
- Set value for an entire row
- >>> df.loc['cobra'] = 10
- >>> df
- max_speed shield
- cobra 10 10
- viper 4 50
- sidewinder 7 50
- Set value for an entire column
- >>> df.loc[:, 'max_speed'] = 30
- >>> df
- max_speed shield
- cobra 30 10
- viper 30 50
- sidewinder 30 50
- Set value for rows matching callable condition
- >>> df.loc[df['shield'] > 35] = 0
- >>> df
- max_speed shield
- cobra 30 10
- viper 0 0
- sidewinder 0 0
- **Getting values on a DataFrame with an index that has integer labels**
- Another example using integers for the index
- >>> df = pd.DataFrame([[1, 2], [4, 5], [7, 8]],
- ... index=[7, 8, 9], columns=['max_speed', 'shield'])
- >>> df
- max_speed shield
- 7 1 2
- 8 4 5
- 9 7 8
- Slice with integer labels for rows. As mentioned above, note that both
- the start and stop of the slice are included.
- >>> df.loc[7:9]
- max_speed shield
- 7 1 2
- 8 4 5
- 9 7 8
- **Getting values with a MultiIndex**
- A number of examples using a DataFrame with a MultiIndex
- >>> tuples = [
- ... ('cobra', 'mark i'), ('cobra', 'mark ii'),
- ... ('sidewinder', 'mark i'), ('sidewinder', 'mark ii'),
- ... ('viper', 'mark ii'), ('viper', 'mark iii')
- ... ]
- >>> index = pd.MultiIndex.from_tuples(tuples)
- >>> values = [[12, 2], [0, 4], [10, 20],
- ... [1, 4], [7, 1], [16, 36]]
- >>> df = pd.DataFrame(values, columns=['max_speed', 'shield'], index=index)
- >>> df
- max_speed shield
- cobra mark i 12 2
- mark ii 0 4
- sidewinder mark i 10 20
- mark ii 1 4
- viper mark ii 7 1
- mark iii 16 36
- Single label. Note this returns a DataFrame with a single index.
- >>> df.loc['cobra']
- max_speed shield
- mark i 12 2
- mark ii 0 4
- Single index tuple. Note this returns a Series.
- >>> df.loc[('cobra', 'mark ii')]
- max_speed 0
- shield 4
- Name: (cobra, mark ii), dtype: int64
- Single label for row and column. Similar to passing in a tuple, this
- returns a Series.
- >>> df.loc['cobra', 'mark i']
- max_speed 12
- shield 2
- Name: (cobra, mark i), dtype: int64
- Single tuple. Note using ``[[]]`` returns a DataFrame.
- >>> df.loc[[('cobra', 'mark ii')]]
- max_speed shield
- cobra mark ii 0 4
- Single tuple for the index with a single label for the column
- >>> df.loc[('cobra', 'mark i'), 'shield']
- 2
- Slice from index tuple to single label
- >>> df.loc[('cobra', 'mark i'):'viper']
- max_speed shield
- cobra mark i 12 2
- mark ii 0 4
- sidewinder mark i 10 20
- mark ii 1 4
- viper mark ii 7 1
- mark iii 16 36
- Slice from index tuple to index tuple
- >>> df.loc[('cobra', 'mark i'):('viper', 'mark ii')]
- max_speed shield
- cobra mark i 12 2
- mark ii 0 4
- sidewinder mark i 10 20
- mark ii 1 4
- viper mark ii 7 1
- """
- return _LocIndexer("loc", self)
- @property
- def at(self) -> "_AtIndexer":
- """
- Access a single value for a row/column label pair.
- Similar to ``loc``, in that both provide label-based lookups. Use
- ``at`` if you only need to get or set a single value in a DataFrame
- or Series.
- Raises
- ------
- KeyError
- If 'label' does not exist in DataFrame.
- See Also
- --------
- DataFrame.iat : Access a single value for a row/column pair by integer
- position.
- DataFrame.loc : Access a group of rows and columns by label(s).
- Series.at : Access a single value using a label.
- Examples
- --------
- >>> df = pd.DataFrame([[0, 2, 3], [0, 4, 1], [10, 20, 30]],
- ... index=[4, 5, 6], columns=['A', 'B', 'C'])
- >>> df
- A B C
- 4 0 2 3
- 5 0 4 1
- 6 10 20 30
- Get value at specified row/column pair
- >>> df.at[4, 'B']
- 2
- Set value at specified row/column pair
- >>> df.at[4, 'B'] = 10
- >>> df.at[4, 'B']
- 10
- Get value within a Series
- >>> df.loc[5].at['B']
- 4
- """
- return _AtIndexer("at", self)
- @property
- def iat(self) -> "_iAtIndexer":
- """
- Access a single value for a row/column pair by integer position.
- Similar to ``iloc``, in that both provide integer-based lookups. Use
- ``iat`` if you only need to get or set a single value in a DataFrame
- or Series.
- Raises
- ------
- IndexError
- When integer position is out of bounds.
- See Also
- --------
- DataFrame.at : Access a single value for a row/column label pair.
- DataFrame.loc : Access a group of rows and columns by label(s).
- DataFrame.iloc : Access a group of rows and columns by integer position(s).
- Examples
- --------
- >>> df = pd.DataFrame([[0, 2, 3], [0, 4, 1], [10, 20, 30]],
- ... columns=['A', 'B', 'C'])
- >>> df
- A B C
- 0 0 2 3
- 1 0 4 1
- 2 10 20 30
- Get value at specified row/column pair
- >>> df.iat[1, 2]
- 1
- Set value at specified row/column pair
- >>> df.iat[1, 2] = 10
- >>> df.iat[1, 2]
- 10
- Get value within a series
- >>> df.loc[0].iat[1]
- 2
- """
- return _iAtIndexer("iat", self)
- class _NDFrameIndexer(_NDFrameIndexerBase):
- _valid_types: str
- axis = None
- def __call__(self, axis=None):
- # we need to return a copy of ourselves
- new_self = type(self)(self.name, self.obj)
- if axis is not None:
- axis = self.obj._get_axis_number(axis)
- new_self.axis = axis
- return new_self
- # TODO: remove once geopandas no longer needs this
- def __getitem__(self, key):
- # Used in ix and downstream in geopandas _CoordinateIndexer
- if type(key) is tuple:
- # Note: we check the type exactly instead of with isinstance
- # because NamedTuple is checked separately.
- key = tuple(com.apply_if_callable(x, self.obj) for x in key)
- try:
- values = self.obj._get_value(*key)
- except (KeyError, TypeError, InvalidIndexError, AttributeError):
- # TypeError occurs here if the key has non-hashable entries,
- # generally slice or list.
- # TODO(ix): most/all of the TypeError cases here are for ix,
- # so this check can be removed once ix is removed.
- # The InvalidIndexError is only catched for compatibility
- # with geopandas, see
- # https://github.com/pandas-dev/pandas/issues/27258
- # TODO: The AttributeError is for IntervalIndex which
- # incorrectly implements get_value, see
- # https://github.com/pandas-dev/pandas/issues/27865
- pass
- else:
- if is_scalar(values):
- return values
- return self._getitem_tuple(key)
- else:
- # we by definition only have the 0th axis
- axis = self.axis or 0
- key = com.apply_if_callable(key, self.obj)
- return self._getitem_axis(key, axis=axis)
- def _get_label(self, label, axis: int):
- if self.ndim == 1:
- # for perf reasons we want to try _xs first
- # as its basically direct indexing
- # but will fail when the index is not present
- # see GH5667
- return self.obj._xs(label, axis=axis)
- elif isinstance(label, tuple) and isinstance(label[axis], slice):
- raise IndexingError("no slices here, handle elsewhere")
- return self.obj._xs(label, axis=axis)
- def _get_loc(self, key: int, axis: int):
- return self.obj._ixs(key, axis=axis)
- def _slice(self, obj, axis: int, kind=None):
- return self.obj._slice(obj, axis=axis, kind=kind)
- def _get_setitem_indexer(self, key):
- if self.axis is not None:
- return self._convert_tuple(key)
- ax = self.obj._get_axis(0)
- if isinstance(ax, ABCMultiIndex) and self.name != "iloc":
- try:
- return ax.get_loc(key)
- except (TypeError, KeyError, InvalidIndexError):
- # TypeError e.g. passed a bool
- pass
- if isinstance(key, tuple):
- try:
- return self._convert_tuple(key)
- except IndexingError:
- pass
- if isinstance(key, range):
- return list(key)
- axis = self.axis or 0
- try:
- return self._convert_to_indexer(key, axis=axis)
- except TypeError as e:
- # invalid indexer type vs 'other' indexing errors
- if "cannot do" in str(e):
- raise
- raise IndexingError(key)
- def __setitem__(self, key, value):
- if isinstance(key, tuple):
- key = tuple(com.apply_if_callable(x, self.obj) for x in key)
- else:
- key = com.apply_if_callable(key, self.obj)
- indexer = self._get_setitem_indexer(key)
- self._setitem_with_indexer(indexer, value)
- def _validate_key(self, key, axis: int):
- """
- Ensure that key is valid for current indexer.
- Parameters
- ----------
- key : scalar, slice or list-like
- Key requested.
- axis : int
- Dimension on which the indexing is being made.
- Raises
- ------
- TypeError
- If the key (or some element of it) has wrong type.
- IndexError
- If the key (or some element of it) is out of bounds.
- KeyError
- If the key was not found.
- """
- raise AbstractMethodError(self)
- def _has_valid_tuple(self, key: Tuple):
- """
- Check the key for valid keys across my indexer.
- """
- for i, k in enumerate(key):
- if i >= self.ndim:
- raise IndexingError("Too many indexers")
- try:
- self._validate_key(k, i)
- except ValueError:
- raise ValueError(
- "Location based indexing can only have "
- f"[{self._valid_types}] types"
- )
- def _is_nested_tuple_indexer(self, tup: Tuple) -> bool:
- """
- Returns
- -------
- bool
- """
- if any(isinstance(ax, ABCMultiIndex) for ax in self.obj.axes):
- return any(is_nested_tuple(tup, ax) for ax in self.obj.axes)
- return False
- def _convert_tuple(self, key):
- keyidx = []
- if self.axis is not None:
- axis = self.obj._get_axis_number(self.axis)
- for i in range(self.ndim):
- if i == axis:
- keyidx.append(self._convert_to_indexer(key, axis=axis))
- else:
- keyidx.append(slice(None))
- else:
- for i, k in enumerate(key):
- if i >= self.ndim:
- raise IndexingError("Too many indexers")
- idx = self._convert_to_indexer(k, axis=i)
- keyidx.append(idx)
- return tuple(keyidx)
- def _convert_scalar_indexer(self, key, axis: int):
- # if we are accessing via lowered dim, use the last dim
- ax = self.obj._get_axis(min(axis, self.ndim - 1))
- # a scalar
- return ax._convert_scalar_indexer(key, kind=self.name)
- def _convert_slice_indexer(self, key: slice, axis: int):
- # if we are accessing via lowered dim, use the last dim
- ax = self.obj._get_axis(min(axis, self.ndim - 1))
- return ax._convert_slice_indexer(key, kind=self.name)
- def _has_valid_setitem_indexer(self, indexer) -> bool:
- return True
- def _has_valid_positional_setitem_indexer(self, indexer) -> bool:
- """
- Validate that a positional indexer cannot enlarge its target
- will raise if needed, does not modify the indexer externally.
- Returns
- -------
- bool
- """
- if isinstance(indexer, dict):
- raise IndexError(f"{self.name} cannot enlarge its target object")
- else:
- if not isinstance(indexer, tuple):
- indexer = _tuplify(self.ndim, indexer)
- for ax, i in zip(self.obj.axes, indexer):
- if isinstance(i, slice):
- # should check the stop slice?
- pass
- elif is_list_like_indexer(i):
- # should check the elements?
- pass
- elif is_integer(i):
- if i >= len(ax):
- raise IndexError(
- f"{self.name} cannot enlarge its target object"
- )
- elif isinstance(i, dict):
- raise IndexError(f"{self.name} cannot enlarge its target object")
- return True
- def _setitem_with_indexer(self, indexer, value):
- self._has_valid_setitem_indexer(indexer)
- # also has the side effect of consolidating in-place
- from pandas import Series
- info_axis = self.obj._info_axis_number
- # maybe partial set
- take_split_path = self.obj._is_mixed_type
- # if there is only one block/type, still have to take split path
- # unless the block is one-dimensional or it can hold the value
- if not take_split_path and self.obj._data.blocks:
- (blk,) = self.obj._data.blocks
- if 1 < blk.ndim: # in case of dict, keys are indices
- val = list(value.values()) if isinstance(value, dict) else value
- take_split_path = not blk._can_hold_element(val)
- # if we have any multi-indexes that have non-trivial slices
- # (not null slices) then we must take the split path, xref
- # GH 10360, GH 27841
- if isinstance(indexer, tuple) and len(indexer) == len(self.obj.axes):
- for i, ax in zip(indexer, self.obj.axes):
- if isinstance(ax, ABCMultiIndex) and not (
- is_integer(i) or com.is_null_slice(i)
- ):
- take_split_path = True
- break
- if isinstance(indexer, tuple):
- nindexer = []
- for i, idx in enumerate(indexer):
- if isinstance(idx, dict):
- # reindex the axis to the new value
- # and set inplace
- key, _ = convert_missing_indexer(idx)
- # if this is the items axes, then take the main missing
- # path first
- # this correctly sets the dtype and avoids cache issues
- # essentially this separates out the block that is needed
- # to possibly be modified
- if self.ndim > 1 and i == self.obj._info_axis_number:
- # add the new item, and set the value
- # must have all defined axes if we have a scalar
- # or a list-like on the non-info axes if we have a
- # list-like
- len_non_info_axes = (
- len(_ax) for _i, _ax in enumerate(self.obj.axes) if _i != i
- )
- if any(not l for l in len_non_info_axes):
- if not is_list_like_indexer(value):
- raise ValueError(
- "cannot set a frame with no "
- "defined index and a scalar"
- )
- self.obj[key] = value
- return self.obj
- # add a new item with the dtype setup
- self.obj[key] = _infer_fill_value(value)
- new_indexer = convert_from_missing_indexer_tuple(
- indexer, self.obj.axes
- )
- self._setitem_with_indexer(new_indexer, value)
- return self.obj
- # reindex the axis
- # make sure to clear the cache because we are
- # just replacing the block manager here
- # so the object is the same
- index = self.obj._get_axis(i)
- labels = index.insert(len(index), key)
- self.obj._data = self.obj.reindex(labels, axis=i)._data
- self.obj._maybe_update_cacher(clear=True)
- self.obj._is_copy = None
- nindexer.append(labels.get_loc(key))
- else:
- nindexer.append(idx)
- indexer = tuple(nindexer)
- else:
- indexer, missing = convert_missing_indexer(indexer)
- if missing:
- return self._setitem_with_indexer_missing(indexer, value)
- # set
- item_labels = self.obj._get_axis(info_axis)
- # align and set the values
- if take_split_path:
- # Above we only set take_split_path to True for 2D cases
- assert self.ndim == 2
- assert info_axis == 1
- if not isinstance(indexer, tuple):
- indexer = _tuplify(self.ndim, indexer)
- if isinstance(value, ABCSeries):
- value = self._align_series(indexer, value)
- info_idx = indexer[info_axis]
- if is_integer(info_idx):
- info_idx = [info_idx]
- labels = item_labels[info_idx]
- # if we have a partial multiindex, then need to adjust the plane
- # indexer here
- if len(labels) == 1 and isinstance(
- self.obj[labels[0]].axes[0], ABCMultiIndex
- ):
- item = labels[0]
- obj = self.obj[item]
- index = obj.index
- idx = indexer[:info_axis][0]
- plane_indexer = tuple([idx]) + indexer[info_axis + 1 :]
- lplane_indexer = length_of_indexer(plane_indexer[0], index)
- # require that we are setting the right number of values that
- # we are indexing
- if (
- is_list_like_indexer(value)
- and np.iterable(value)
- and lplane_indexer != len(value)
- ):
- if len(obj[idx]) != len(value):
- raise ValueError(
- "cannot set using a multi-index "
- "selection indexer with a different "
- "length than the value"
- )
- # make sure we have an ndarray
- value = getattr(value, "values", value).ravel()
- # we can directly set the series here
- # as we select a slice indexer on the mi
- if isinstance(idx, slice):
- idx = index._convert_slice_indexer(idx)
- obj._consolidate_inplace()
- obj = obj.copy()
- obj._data = obj._data.setitem(indexer=tuple([idx]), value=value)
- self.obj[item] = obj
- return
- # non-mi
- else:
- plane_indexer = indexer[:info_axis] + indexer[info_axis + 1 :]
- plane_axis = self.obj.axes[:info_axis][0]
- lplane_indexer = length_of_indexer(plane_indexer[0], plane_axis)
- def setter(item, v):
- s = self.obj[item]
- pi = plane_indexer[0] if lplane_indexer == 1 else plane_indexer
- # perform the equivalent of a setitem on the info axis
- # as we have a null slice or a slice with full bounds
- # which means essentially reassign to the columns of a
- # multi-dim object
- # GH6149 (null slice), GH10408 (full bounds)
- if isinstance(pi, tuple) and all(
- com.is_null_slice(idx) or com.is_full_slice(idx, len(self.obj))
- for idx in pi
- ):
- s = v
- else:
- # set the item, possibly having a dtype change
- s._consolidate_inplace()
- s = s.copy()
- s._data = s._data.setitem(indexer=pi, value=v)
- s._maybe_update_cacher(clear=True)
- # reset the sliced object if unique
- self.obj[item] = s
- # we need an iterable, with a ndim of at least 1
- # eg. don't pass through np.array(0)
- if is_list_like_indexer(value) and getattr(value, "ndim", 1) > 0:
- # we have an equal len Frame
- if isinstance(value, ABCDataFrame):
- sub_indexer = list(indexer)
- multiindex_indexer = isinstance(labels, ABCMultiIndex)
- for item in labels:
- if item in value:
- sub_indexer[info_axis] = item
- v = self._align_series(
- tuple(sub_indexer), value[item], multiindex_indexer
- )
- else:
- v = np.nan
- setter(item, v)
- # we have an equal len ndarray/convertible to our labels
- # hasattr first, to avoid coercing to ndarray without reason.
- # But we may be relying on the ndarray coercion to check ndim.
- # Why not just convert to an ndarray earlier on if needed?
- elif np.ndim(value) == 2:
- # note that this coerces the dtype if we are mixed
- # GH 7551
- value = np.array(value, dtype=object)
- if len(labels) != value.shape[1]:
- raise ValueError(
- "Must have equal len keys and value "
- "when setting with an ndarray"
- )
- for i, item in enumerate(labels):
- # setting with a list, recoerces
- setter(item, value[:, i].tolist())
- # we have an equal len list/ndarray
- elif _can_do_equal_len(
- labels, value, plane_indexer, lplane_indexer, self.obj
- ):
- setter(labels[0], value)
- # per label values
- else:
- if len(labels) != len(value):
- raise ValueError(
- "Must have equal len keys and value "
- "when setting with an iterable"
- )
- for item, v in zip(labels, value):
- setter(item, v)
- else:
- # scalar
- for item in labels:
- setter(item, value)
- else:
- if isinstance(indexer, tuple):
- indexer = maybe_convert_ix(*indexer)
- # if we are setting on the info axis ONLY
- # set using those methods to avoid block-splitting
- # logic here
- if (
- len(indexer) > info_axis
- and is_integer(indexer[info_axis])
- and all(
- com.is_null_slice(idx)
- for i, idx in enumerate(indexer)
- if i != info_axis
- )
- and item_labels.is_unique
- ):
- self.obj[item_labels[indexer[info_axis]]] = value
- return
- if isinstance(value, (ABCSeries, dict)):
- # TODO(EA): ExtensionBlock.setitem this causes issues with
- # setting for extensionarrays that store dicts. Need to decide
- # if it's worth supporting that.
- value = self._align_series(indexer, Series(value))
- elif isinstance(value, ABCDataFrame):
- value = self._align_frame(indexer, value)
- # check for chained assignment
- self.obj._check_is_chained_assignment_possible()
- # actually do the set
- self.obj._consolidate_inplace()
- self.obj._data = self.obj._data.setitem(indexer=indexer, value=value)
- self.obj._maybe_update_cacher(clear=True)
- def _setitem_with_indexer_missing(self, indexer, value):
- """
- Insert new row(s) or column(s) into the Series or DataFrame.
- """
- from pandas import Series
- # reindex the axis to the new value
- # and set inplace
- if self.ndim == 1:
- index = self.obj.index
- new_index = index.insert(len(index), indexer)
- # we have a coerced indexer, e.g. a float
- # that matches in an Int64Index, so
- # we will not create a duplicate index, rather
- # index to that element
- # e.g. 0.0 -> 0
- # GH#12246
- if index.is_unique:
- new_indexer = index.get_indexer([new_index[-1]])
- if (new_indexer != -1).any():
- return self._setitem_with_indexer(new_indexer, value)
- # this preserves dtype of the value
- new_values = Series([value])._values
- if len(self.obj._values):
- # GH#22717 handle casting compatibility that np.concatenate
- # does incorrectly
- new_values = concat_compat([self.obj._values, new_values])
- self.obj._data = self.obj._constructor(
- new_values, index=new_index, name=self.obj.name
- )._data
- self.obj._maybe_update_cacher(clear=True)
- return self.obj
- elif self.ndim == 2:
- if not len(self.obj.columns):
- # no columns and scalar
- raise ValueError("cannot set a frame with no defined columns")
- if isinstance(value, ABCSeries):
- # append a Series
- value = value.reindex(index=self.obj.columns, copy=True)
- value.name = indexer
- else:
- # a list-list
- if is_list_like_indexer(value):
- # must have conforming columns
- if len(value) != len(self.obj.columns):
- raise ValueError("cannot set a row with mismatched columns")
- value = Series(value, index=self.obj.columns, name=indexer)
- self.obj._data = self.obj.append(value)._data
- self.obj._maybe_update_cacher(clear=True)
- return self.obj
- def _align_series(self, indexer, ser: ABCSeries, multiindex_indexer: bool = False):
- """
- Parameters
- ----------
- indexer : tuple, slice, scalar
- Indexer used to get the locations that will be set to `ser`.
- ser : pd.Series
- Values to assign to the locations specified by `indexer`.
- multiindex_indexer : boolean, optional
- Defaults to False. Should be set to True if `indexer` was from
- a `pd.MultiIndex`, to avoid unnecessary broadcasting.
- Returns
- -------
- `np.array` of `ser` broadcast to the appropriate shape for assignment
- to the locations selected by `indexer`
- """
- if isinstance(indexer, (slice, np.ndarray, list, Index)):
- indexer = tuple([indexer])
- if isinstance(indexer, tuple):
- # flatten np.ndarray indexers
- def ravel(i):
- return i.ravel() if isinstance(i, np.ndarray) else i
- indexer = tuple(map(ravel, indexer))
- aligners = [not com.is_null_slice(idx) for idx in indexer]
- sum_aligners = sum(aligners)
- single_aligner = sum_aligners == 1
- is_frame = self.ndim == 2
- obj = self.obj
- # are we a single alignable value on a non-primary
- # dim (e.g. panel: 1,2, or frame: 0) ?
- # hence need to align to a single axis dimension
- # rather that find all valid dims
- # frame
- if is_frame:
- single_aligner = single_aligner and aligners[0]
- # we have a frame, with multiple indexers on both axes; and a
- # series, so need to broadcast (see GH5206)
- if sum_aligners == self.ndim and all(is_sequence(_) for _ in indexer):
- ser = ser.reindex(obj.axes[0][indexer[0]], copy=True)._values
- # single indexer
- if len(indexer) > 1 and not multiindex_indexer:
- len_indexer = len(indexer[1])
- ser = np.tile(ser, len_indexer).reshape(len_indexer, -1).T
- return ser
- for i, idx in enumerate(indexer):
- ax = obj.axes[i]
- # multiple aligners (or null slices)
- if is_sequence(idx) or isinstance(idx, slice):
- if single_aligner and com.is_null_slice(idx):
- continue
- new_ix = ax[idx]
- if not is_list_like_indexer(new_ix):
- new_ix = Index([new_ix])
- else:
- new_ix = Index(new_ix)
- if ser.index.equals(new_ix) or not len(new_ix):
- return ser._values.copy()
- return ser.reindex(new_ix)._values
- # 2 dims
- elif single_aligner:
- # reindex along index
- ax = self.obj.axes[1]
- if ser.index.equals(ax) or not len(ax):
- return ser._values.copy()
- return ser.reindex(ax)._values
- elif is_scalar(indexer):
- ax = self.obj._get_axis(1)
- if ser.index.equals(ax):
- return ser._values.copy()
- return ser.reindex(ax)._values
- raise ValueError("Incompatible indexer with Series")
- def _align_frame(self, indexer, df: ABCDataFrame):
- is_frame = self.ndim == 2
- if isinstance(indexer, tuple):
- idx, cols = None, None
- sindexers = []
- for i, ix in enumerate(indexer):
- ax = self.obj.axes[i]
- if is_sequence(ix) or isinstance(ix, slice):
- if isinstance(ix, np.ndarray):
- ix = ix.ravel()
- if idx is None:
- idx = ax[ix]
- elif cols is None:
- cols = ax[ix]
- else:
- break
- else:
- sindexers.append(i)
- if idx is not None and cols is not None:
- if df.index.equals(idx) and df.columns.equals(cols):
- val = df.copy()._values
- else:
- val = df.reindex(idx, columns=cols)._values
- return val
- elif (isinstance(indexer, slice) or is_list_like_indexer(indexer)) and is_frame:
- ax = self.obj.index[indexer]
- if df.index.equals(ax):
- val = df.copy()._values
- else:
- # we have a multi-index and are trying to align
- # with a particular, level GH3738
- if (
- isinstance(ax, ABCMultiIndex)
- and isinstance(df.index, ABCMultiIndex)
- and ax.nlevels != df.index.nlevels
- ):
- raise TypeError(
- "cannot align on a multi-index with out "
- "specifying the join levels"
- )
- val = df.reindex(index=ax)._values
- return val
- raise ValueError("Incompatible indexer with DataFrame")
- def _getitem_tuple(self, tup: Tuple):
- try:
- return self._getitem_lowerdim(tup)
- except IndexingError:
- pass
- # no multi-index, so validate all of the indexers
- self._has_valid_tuple(tup)
- # ugly hack for GH #836
- if self._multi_take_opportunity(tup):
- return self._multi_take(tup)
- # no shortcut needed
- retval = self.obj
- for i, key in enumerate(tup):
- if com.is_null_slice(key):
- continue
- retval = getattr(retval, self.name)._getitem_axis(key, axis=i)
- return retval
- def _multi_take_opportunity(self, tup: Tuple) -> bool:
- """
- Check whether there is the possibility to use ``_multi_take``.
- Currently the limit is that all axes being indexed, must be indexed with
- list-likes.
- Parameters
- ----------
- tup : tuple
- Tuple of indexers, one per axis.
- Returns
- -------
- bool
- Whether the current indexing,
- can be passed through `_multi_take`.
- """
- if not all(is_list_like_indexer(x) for x in tup):
- return False
- # just too complicated
- if any(com.is_bool_indexer(x) for x in tup):
- return False
- return True
- def _multi_take(self, tup: Tuple):
- """
- Create the indexers for the passed tuple of keys, and
- executes the take operation. This allows the take operation to be
- executed all at once, rather than once for each dimension.
- Improving efficiency.
- Parameters
- ----------
- tup : tuple
- Tuple of indexers, one per axis.
- Returns
- -------
- values: same type as the object being indexed
- """
- # GH 836
- o = self.obj
- d = {
- axis: self._get_listlike_indexer(key, axis)
- for (key, axis) in zip(tup, o._AXIS_ORDERS)
- }
- return o._reindex_with_indexers(d, copy=True, allow_dups=True)
- def _convert_for_reindex(self, key, axis: int):
- return key
- def _handle_lowerdim_multi_index_axis0(self, tup: Tuple):
- # we have an axis0 multi-index, handle or raise
- axis = self.axis or 0
- try:
- # fast path for series or for tup devoid of slices
- return self._get_label(tup, axis=axis)
- except TypeError:
- # slices are unhashable
- pass
- except KeyError as ek:
- # raise KeyError if number of indexers match
- # else IndexingError will be raised
- if len(tup) <= self.obj.index.nlevels and len(tup) > self.ndim:
- raise ek
- return None
- def _getitem_lowerdim(self, tup: Tuple):
- # we can directly get the axis result since the axis is specified
- if self.axis is not None:
- axis = self.obj._get_axis_number(self.axis)
- return self._getitem_axis(tup, axis=axis)
- # we may have a nested tuples indexer here
- if self._is_nested_tuple_indexer(tup):
- return self._getitem_nested_tuple(tup)
- # we maybe be using a tuple to represent multiple dimensions here
- ax0 = self.obj._get_axis(0)
- # ...but iloc should handle the tuple as simple integer-location
- # instead of checking it as multiindex representation (GH 13797)
- if isinstance(ax0, ABCMultiIndex) and self.name != "iloc":
- result = self._handle_lowerdim_multi_index_axis0(tup)
- if result is not None:
- return result
- if len(tup) > self.ndim:
- raise IndexingError("Too many indexers. handle elsewhere")
- for i, key in enumerate(tup):
- if is_label_like(key) or isinstance(key, tuple):
- section = self._getitem_axis(key, axis=i)
- # we have yielded a scalar ?
- if not is_list_like_indexer(section):
- return section
- elif section.ndim == self.ndim:
- # we're in the middle of slicing through a MultiIndex
- # revise the key wrt to `section` by inserting an _NS
- new_key = tup[:i] + (_NS,) + tup[i + 1 :]
- else:
- new_key = tup[:i] + tup[i + 1 :]
- # unfortunately need an odious kludge here because of
- # DataFrame transposing convention
- if (
- isinstance(section, ABCDataFrame)
- and i > 0
- and len(new_key) == 2
- ):
- a, b = new_key
- new_key = b, a
- if len(new_key) == 1:
- new_key = new_key[0]
- # Slices should return views, but calling iloc/loc with a null
- # slice returns a new object.
- if com.is_null_slice(new_key):
- return section
- # This is an elided recursive call to iloc/loc/etc'
- return getattr(section, self.name)[new_key]
- raise IndexingError("not applicable")
- def _getitem_nested_tuple(self, tup: Tuple):
- # we have a nested tuple so have at least 1 multi-index level
- # we should be able to match up the dimensionality here
- # we have too many indexers for our dim, but have at least 1
- # multi-index dimension, try to see if we have something like
- # a tuple passed to a series with a multi-index
- if len(tup) > self.ndim:
- result = self._handle_lowerdim_multi_index_axis0(tup)
- if result is not None:
- return result
- # this is a series with a multi-index specified a tuple of
- # selectors
- axis = self.axis or 0
- return self._getitem_axis(tup, axis=axis)
- # handle the multi-axis by taking sections and reducing
- # this is iterative
- obj = self.obj
- axis = 0
- for i, key in enumerate(tup):
- if com.is_null_slice(key):
- axis += 1
- continue
- current_ndim = obj.ndim
- obj = getattr(obj, self.name)._getitem_axis(key, axis=axis)
- axis += 1
- # if we have a scalar, we are done
- if is_scalar(obj) or not hasattr(obj, "ndim"):
- break
- # has the dim of the obj changed?
- # GH 7199
- if obj.ndim < current_ndim:
- axis -= 1
- return obj
- # TODO: remove once geopandas no longer needs __getitem__
- def _getitem_axis(self, key, axis: int):
- if is_iterator(key):
- key = list(key)
- self._validate_key(key, axis)
- labels = self.obj._get_axis(axis)
- if isinstance(key, slice):
- return self._get_slice_axis(key, axis=axis)
- elif is_list_like_indexer(key) and not (
- isinstance(key, tuple) and isinstance(labels, ABCMultiIndex)
- ):
- if hasattr(key, "ndim") and key.ndim > 1:
- raise ValueError("Cannot index with multidimensional key")
- return self._getitem_iterable(key, axis=axis)
- else:
- # maybe coerce a float scalar to integer
- key = labels._maybe_cast_indexer(key)
- if is_integer(key):
- if axis == 0 and isinstance(labels, ABCMultiIndex):
- try:
- return self._get_label(key, axis=axis)
- except (KeyError, TypeError):
- if self.obj.index.levels[0].is_integer():
- raise
- # this is the fallback! (for a non-float, non-integer index)
- if not labels.is_floating() and not labels.is_integer():
- return self._get_loc(key, axis=axis)
- return self._get_label(key, axis=axis)
- def _get_listlike_indexer(self, key, axis: int, raise_missing: bool = False):
- """
- Transform a list-like of keys into a new index and an indexer.
- Parameters
- ----------
- key : list-like
- Targeted labels.
- axis: int
- Dimension on which the indexing is being made.
- raise_missing: bool, default False
- Whether to raise a KeyError if some labels were not found.
- Will be removed in the future, and then this method will always behave as
- if ``raise_missing=True``.
- Raises
- ------
- KeyError
- If at least one key was requested but none was found, and
- raise_missing=True.
- Returns
- -------
- keyarr: Index
- New index (coinciding with 'key' if the axis is unique).
- values : array-like
- Indexer for the return object, -1 denotes keys not found.
- """
- o = self.obj
- ax = o._get_axis(axis)
- # Have the index compute an indexer or return None
- # if it cannot handle:
- indexer, keyarr = ax._convert_listlike_indexer(key, kind=self.name)
- # We only act on all found values:
- if indexer is not None and (indexer != -1).all():
- self._validate_read_indexer(key, indexer, axis, raise_missing=raise_missing)
- return ax[indexer], indexer
- if ax.is_unique and not getattr(ax, "is_overlapping", False):
- # If we are trying to get actual keys from empty Series, we
- # patiently wait for a KeyError later on - otherwise, convert
- if len(ax) or not len(key):
- key = self._convert_for_reindex(key, axis)
- indexer = ax.get_indexer_for(key)
- keyarr = ax.reindex(keyarr)[0]
- else:
- keyarr, indexer, new_indexer = ax._reindex_non_unique(keyarr)
- self._validate_read_indexer(
- keyarr, indexer, o._get_axis_number(axis), raise_missing=raise_missing
- )
- return keyarr, indexer
- def _getitem_iterable(self, key, axis: int):
- """
- Index current object with an an iterable key.
- The iterable key can be a boolean indexer or a collection of keys.
- Parameters
- ----------
- key : iterable
- Targeted labels or boolean indexer.
- axis: int
- Dimension on which the indexing is being made.
- Raises
- ------
- KeyError
- If no key was found. Will change in the future to raise if not all
- keys were found.
- IndexingError
- If the boolean indexer is unalignable with the object being
- indexed.
- Returns
- -------
- scalar, DataFrame, or Series: indexed value(s).
- """
- # caller is responsible for ensuring non-None axis
- self._validate_key(key, axis)
- labels = self.obj._get_axis(axis)
- if com.is_bool_indexer(key):
- # A boolean indexer
- key = check_bool_indexer(labels, key)
- (inds,) = key.nonzero()
- return self.obj._take_with_is_copy(inds, axis=axis)
- else:
- # A collection of keys
- keyarr, indexer = self._get_listlike_indexer(key, axis, raise_missing=False)
- return self.obj._reindex_with_indexers(
- {axis: [keyarr, indexer]}, copy=True, allow_dups=True
- )
- def _validate_read_indexer(
- self, key, indexer, axis: int, raise_missing: bool = False
- ):
- """
- Check that indexer can be used to return a result.
- e.g. at least one element was found,
- unless the list of keys was actually empty.
- Parameters
- ----------
- key : list-like
- Targeted labels (only used to show correct error message).
- indexer: array-like of booleans
- Indices corresponding to the key,
- (with -1 indicating not found).
- axis: int
- Dimension on which the indexing is being made.
- raise_missing: bool
- Whether to raise a KeyError if some labels are not found. Will be
- removed in the future, and then this method will always behave as
- if raise_missing=True.
- Raises
- ------
- KeyError
- If at least one key was requested but none was found, and
- raise_missing=True.
- """
- ax = self.obj._get_axis(axis)
- if len(key) == 0:
- return
- # Count missing values:
- missing = (indexer < 0).sum()
- if missing:
- if missing == len(indexer):
- axis_name = self.obj._get_axis_name(axis)
- raise KeyError(f"None of [{key}] are in the [{axis_name}]")
- # We (temporarily) allow for some missing keys with .loc, except in
- # some cases (e.g. setting) in which "raise_missing" will be False
- if not (self.name == "loc" and not raise_missing):
- not_found = list(set(key) - set(ax))
- raise KeyError(f"{not_found} not in index")
- # we skip the warning on Categorical/Interval
- # as this check is actually done (check for
- # non-missing values), but a bit later in the
- # code, so we want to avoid warning & then
- # just raising
- if not (ax.is_categorical() or ax.is_interval()):
- raise KeyError(
- "Passing list-likes to .loc or [] with any missing labels "
- "is no longer supported, see "
- "https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#deprecate-loc-reindex-listlike" # noqa:E501
- )
- def _convert_to_indexer(self, obj, axis: int, raise_missing: bool = False):
- """
- Convert indexing key into something we can use to do actual fancy
- indexing on a ndarray.
- Examples
- ix[:5] -> slice(0, 5)
- ix[[1,2,3]] -> [1,2,3]
- ix[['foo', 'bar', 'baz']] -> [i, j, k] (indices of foo, bar, baz)
- Going by Zen of Python?
- 'In the face of ambiguity, refuse the temptation to guess.'
- raise AmbiguousIndexError with integer labels?
- - No, prefer label-based indexing
- """
- labels = self.obj._get_axis(axis)
- if isinstance(obj, slice):
- return self._convert_slice_indexer(obj, axis)
- # try to find out correct indexer, if not type correct raise
- try:
- obj = self._convert_scalar_indexer(obj, axis)
- except TypeError:
- # but we will allow setting
- pass
- # see if we are positional in nature
- is_int_index = labels.is_integer()
- is_int_positional = is_integer(obj) and not is_int_index
- # if we are a label return me
- try:
- return labels.get_loc(obj)
- except LookupError:
- if isinstance(obj, tuple) and isinstance(labels, ABCMultiIndex):
- if len(obj) == labels.nlevels:
- return {"key": obj}
- raise
- except TypeError:
- pass
- except ValueError:
- if not is_int_positional:
- raise
- # a positional
- if is_int_positional:
- # if we are setting and its not a valid location
- # its an insert which fails by definition
- if self.name == "loc":
- # always valid
- return {"key": obj}
- if obj >= self.obj.shape[axis] and not isinstance(labels, ABCMultiIndex):
- # a positional
- raise ValueError("cannot set by positional indexing with enlargement")
- return obj
- if is_nested_tuple(obj, labels):
- return labels.get_locs(obj)
- elif is_list_like_indexer(obj):
- if com.is_bool_indexer(obj):
- obj = check_bool_indexer(labels, obj)
- (inds,) = obj.nonzero()
- return inds
- else:
- # When setting, missing keys are not allowed, even with .loc:
- return self._get_listlike_indexer(obj, axis, raise_missing=True)[1]
- else:
- try:
- return labels.get_loc(obj)
- except LookupError:
- # allow a not found key only if we are a setter
- if not is_list_like_indexer(obj):
- return {"key": obj}
- raise
- def _get_slice_axis(self, slice_obj: slice, axis: int):
- # caller is responsible for ensuring non-None axis
- obj = self.obj
- if not need_slice(slice_obj):
- return obj.copy(deep=False)
- indexer = self._convert_slice_indexer(slice_obj, axis)
- return self._slice(indexer, axis=axis, kind="iloc")
- class _LocationIndexer(_NDFrameIndexer):
- def __getitem__(self, key):
- if type(key) is tuple:
- key = tuple(com.apply_if_callable(x, self.obj) for x in key)
- if self._is_scalar_access(key):
- try:
- return self._getitem_scalar(key)
- except (KeyError, IndexError, AttributeError):
- pass
- return self._getitem_tuple(key)
- else:
- # we by definition only have the 0th axis
- axis = self.axis or 0
- maybe_callable = com.apply_if_callable(key, self.obj)
- return self._getitem_axis(maybe_callable, axis=axis)
- def _is_scalar_access(self, key: Tuple):
- raise NotImplementedError()
- def _getitem_scalar(self, key):
- raise NotImplementedError()
- def _getitem_axis(self, key, axis: int):
- raise NotImplementedError()
- def _getbool_axis(self, key, axis: int):
- # caller is responsible for ensuring non-None axis
- labels = self.obj._get_axis(axis)
- key = check_bool_indexer(labels, key)
- inds = key.nonzero()[0]
- return self.obj._take_with_is_copy(inds, axis=axis)
- def _get_slice_axis(self, slice_obj: slice, axis: int):
- """
- This is pretty simple as we just have to deal with labels.
- """
- # caller is responsible for ensuring non-None axis
- obj = self.obj
- if not need_slice(slice_obj):
- return obj.copy(deep=False)
- labels = obj._get_axis(axis)
- indexer = labels.slice_indexer(
- slice_obj.start, slice_obj.stop, slice_obj.step, kind=self.name
- )
- if isinstance(indexer, slice):
- return self._slice(indexer, axis=axis, kind="iloc")
- else:
- # DatetimeIndex overrides Index.slice_indexer and may
- # return a DatetimeIndex instead of a slice object.
- return self.obj._take_with_is_copy(indexer, axis=axis)
- @Appender(IndexingMixin.loc.__doc__)
- class _LocIndexer(_LocationIndexer):
- _valid_types = (
- "labels (MUST BE IN THE INDEX), slices of labels (BOTH "
- "endpoints included! Can be slices of integers if the "
- "index is integers), listlike of labels, boolean"
- )
- @Appender(_NDFrameIndexer._validate_key.__doc__)
- def _validate_key(self, key, axis: int):
- # valid for a collection of labels (we check their presence later)
- # slice of labels (where start-end in labels)
- # slice of integers (only if in the labels)
- # boolean
- if isinstance(key, slice):
- return
- if com.is_bool_indexer(key):
- return
- if not is_list_like_indexer(key):
- self._convert_scalar_indexer(key, axis)
- def _is_scalar_access(self, key: Tuple) -> bool:
- """
- Returns
- -------
- bool
- """
- # this is a shortcut accessor to both .loc and .iloc
- # that provide the equivalent access of .at and .iat
- # a) avoid getting things via sections and (to minimize dtype changes)
- # b) provide a performant path
- if len(key) != self.ndim:
- return False
- for i, k in enumerate(key):
- if not is_scalar(k):
- return False
- ax = self.obj.axes[i]
- if isinstance(ax, ABCMultiIndex):
- return False
- if isinstance(k, str) and ax._supports_partial_string_indexing:
- # partial string indexing, df.loc['2000', 'A']
- # should not be considered scalar
- return False
- if not ax.is_unique:
- return False
- return True
- def _getitem_scalar(self, key):
- # a fast-path to scalar access
- # if not, raise
- values = self.obj._get_value(*key)
- return values
- def _get_partial_string_timestamp_match_key(self, key, labels):
- """
- Translate any partial string timestamp matches in key, returning the
- new key.
- (GH 10331)
- """
- if isinstance(labels, ABCMultiIndex):
- if (
- isinstance(key, str)
- and labels.levels[0]._supports_partial_string_indexing
- ):
- # Convert key '2016-01-01' to
- # ('2016-01-01'[, slice(None, None, None)]+)
- key = tuple([key] + [slice(None)] * (len(labels.levels) - 1))
- if isinstance(key, tuple):
- # Convert (..., '2016-01-01', ...) in tuple to
- # (..., slice('2016-01-01', '2016-01-01', None), ...)
- new_key = []
- for i, component in enumerate(key):
- if (
- isinstance(component, str)
- and labels.levels[i]._supports_partial_string_indexing
- ):
- new_key.append(slice(component, component, None))
- else:
- new_key.append(component)
- key = tuple(new_key)
- return key
- def _getitem_axis(self, key, axis: int):
- key = item_from_zerodim(key)
- if is_iterator(key):
- key = list(key)
- labels = self.obj._get_axis(axis)
- key = self._get_partial_string_timestamp_match_key(key, labels)
- if isinstance(key, slice):
- self._validate_key(key, axis)
- return self._get_slice_axis(key, axis=axis)
- elif com.is_bool_indexer(key):
- return self._getbool_axis(key, axis=axis)
- elif is_list_like_indexer(key):
- # convert various list-like indexers
- # to a list of keys
- # we will use the *values* of the object
- # and NOT the index if its a PandasObject
- if isinstance(labels, ABCMultiIndex):
- if isinstance(key, (ABCSeries, np.ndarray)) and key.ndim <= 1:
- # Series, or 0,1 ndim ndarray
- # GH 14730
- key = list(key)
- elif isinstance(key, ABCDataFrame):
- # GH 15438
- raise NotImplementedError(
- "Indexing a MultiIndex with a "
- "DataFrame key is not "
- "implemented"
- )
- elif hasattr(key, "ndim") and key.ndim > 1:
- raise NotImplementedError(
- "Indexing a MultiIndex with a "
- "multidimensional key is not "
- "implemented"
- )
- if (
- not isinstance(key, tuple)
- and len(key)
- and not isinstance(key[0], tuple)
- ):
- key = tuple([key])
- # an iterable multi-selection
- if not (isinstance(key, tuple) and isinstance(labels, ABCMultiIndex)):
- if hasattr(key, "ndim") and key.ndim > 1:
- raise ValueError("Cannot index with multidimensional key")
- return self._getitem_iterable(key, axis=axis)
- # nested tuple slicing
- if is_nested_tuple(key, labels):
- locs = labels.get_locs(key)
- indexer = [slice(None)] * self.ndim
- indexer[axis] = locs
- return self.obj.iloc[tuple(indexer)]
- # fall thru to straight lookup
- self._validate_key(key, axis)
- return self._get_label(key, axis=axis)
- @Appender(IndexingMixin.iloc.__doc__)
- class _iLocIndexer(_LocationIndexer):
- _valid_types = (
- "integer, integer slice (START point is INCLUDED, END "
- "point is EXCLUDED), listlike of integers, boolean array"
- )
- _get_slice_axis = _NDFrameIndexer._get_slice_axis
- def _validate_key(self, key, axis: int):
- if com.is_bool_indexer(key):
- if hasattr(key, "index") and isinstance(key.index, Index):
- if key.index.inferred_type == "integer":
- raise NotImplementedError(
- "iLocation based boolean "
- "indexing on an integer type "
- "is not available"
- )
- raise ValueError(
- "iLocation based boolean indexing cannot use "
- "an indexable as a mask"
- )
- return
- if isinstance(key, slice):
- return
- elif is_integer(key):
- self._validate_integer(key, axis)
- elif isinstance(key, tuple):
- # a tuple should already have been caught by this point
- # so don't treat a tuple as a valid indexer
- raise IndexingError("Too many indexers")
- elif is_list_like_indexer(key):
- arr = np.array(key)
- len_axis = len(self.obj._get_axis(axis))
- # check that the key has a numeric dtype
- if not is_numeric_dtype(arr.dtype):
- raise IndexError(f".iloc requires numeric indexers, got {arr}")
- # check that the key does not exceed the maximum size of the index
- if len(arr) and (arr.max() >= len_axis or arr.min() < -len_axis):
- raise IndexError("positional indexers are out-of-bounds")
- else:
- raise ValueError(f"Can only index by location with a [{self._valid_types}]")
- def _has_valid_setitem_indexer(self, indexer):
- self._has_valid_positional_setitem_indexer(indexer)
- def _is_scalar_access(self, key: Tuple) -> bool:
- """
- Returns
- -------
- bool
- """
- # this is a shortcut accessor to both .loc and .iloc
- # that provide the equivalent access of .at and .iat
- # a) avoid getting things via sections and (to minimize dtype changes)
- # b) provide a performant path
- if len(key) != self.ndim:
- return False
- for i, k in enumerate(key):
- if not is_integer(k):
- return False
- ax = self.obj.axes[i]
- if not ax.is_unique:
- return False
- return True
- def _getitem_scalar(self, key):
- # a fast-path to scalar access
- # if not, raise
- values = self.obj._get_value(*key, takeable=True)
- return values
- def _validate_integer(self, key: int, axis: int) -> None:
- """
- Check that 'key' is a valid position in the desired axis.
- Parameters
- ----------
- key : int
- Requested position.
- axis : int
- Desired axis.
- Raises
- ------
- IndexError
- If 'key' is not a valid position in axis 'axis'.
- """
- len_axis = len(self.obj._get_axis(axis))
- if key >= len_axis or key < -len_axis:
- raise IndexError("single positional indexer is out-of-bounds")
- def _getitem_tuple(self, tup: Tuple):
- self._has_valid_tuple(tup)
- try:
- return self._getitem_lowerdim(tup)
- except IndexingError:
- pass
- retval = self.obj
- axis = 0
- for i, key in enumerate(tup):
- if com.is_null_slice(key):
- axis += 1
- continue
- retval = getattr(retval, self.name)._getitem_axis(key, axis=axis)
- # if the dim was reduced, then pass a lower-dim the next time
- if retval.ndim < self.ndim:
- # TODO: this is never reached in tests; can we confirm that
- # it is impossible?
- axis -= 1
- # try to get for the next axis
- axis += 1
- return retval
- def _get_list_axis(self, key, axis: int):
- """
- Return Series values by list or array of integers.
- Parameters
- ----------
- key : list-like positional indexer
- axis : int
- Returns
- -------
- Series object
- Notes
- -----
- `axis` can only be zero.
- """
- try:
- return self.obj._take_with_is_copy(key, axis=axis)
- except IndexError:
- # re-raise with different error message
- raise IndexError("positional indexers are out-of-bounds")
- def _getitem_axis(self, key, axis: int):
- if isinstance(key, slice):
- return self._get_slice_axis(key, axis=axis)
- if isinstance(key, list):
- key = np.asarray(key)
- if com.is_bool_indexer(key):
- self._validate_key(key, axis)
- return self._getbool_axis(key, axis=axis)
- # a list of integers
- elif is_list_like_indexer(key):
- return self._get_list_axis(key, axis=axis)
- # a single integer
- else:
- key = item_from_zerodim(key)
- if not is_integer(key):
- raise TypeError("Cannot index by location index with a non-integer key")
- # validate the location
- self._validate_integer(key, axis)
- return self._get_loc(key, axis=axis)
- # raise_missing is included for compat with the parent class signature
- def _convert_to_indexer(self, obj, axis: int, raise_missing: bool = False):
- """
- Much simpler as we only have to deal with our valid types.
- """
- # make need to convert a float key
- if isinstance(obj, slice):
- return self._convert_slice_indexer(obj, axis)
- elif is_float(obj):
- return self._convert_scalar_indexer(obj, axis)
- try:
- self._validate_key(obj, axis)
- return obj
- except ValueError:
- raise ValueError(f"Can only index by location with a [{self._valid_types}]")
- class _ScalarAccessIndexer(_NDFrameIndexerBase):
- """
- Access scalars quickly.
- """
- def _convert_key(self, key, is_setter: bool = False):
- raise AbstractMethodError(self)
- def __getitem__(self, key):
- if not isinstance(key, tuple):
- # we could have a convertible item here (e.g. Timestamp)
- if not is_list_like_indexer(key):
- key = tuple([key])
- else:
- raise ValueError("Invalid call for scalar access (getting)!")
- key = self._convert_key(key)
- return self.obj._get_value(*key, takeable=self._takeable)
- def __setitem__(self, key, value):
- if isinstance(key, tuple):
- key = tuple(com.apply_if_callable(x, self.obj) for x in key)
- else:
- # scalar callable may return tuple
- key = com.apply_if_callable(key, self.obj)
- if not isinstance(key, tuple):
- key = _tuplify(self.ndim, key)
- if len(key) != self.ndim:
- raise ValueError("Not enough indexers for scalar access (setting)!")
- key = list(self._convert_key(key, is_setter=True))
- key.append(value)
- self.obj._set_value(*key, takeable=self._takeable)
- @Appender(IndexingMixin.at.__doc__)
- class _AtIndexer(_ScalarAccessIndexer):
- _takeable = False
- def _convert_key(self, key, is_setter: bool = False):
- """
- Require they keys to be the same type as the index. (so we don't
- fallback)
- """
- # allow arbitrary setting
- if is_setter:
- return list(key)
- for ax, i in zip(self.obj.axes, key):
- if ax.is_integer():
- if not is_integer(i):
- raise ValueError(
- "At based indexing on an integer index "
- "can only have integer indexers"
- )
- else:
- if is_integer(i) and not ax.holds_integer():
- raise ValueError(
- "At based indexing on an non-integer "
- "index can only have non-integer "
- "indexers"
- )
- return key
- @Appender(IndexingMixin.iat.__doc__)
- class _iAtIndexer(_ScalarAccessIndexer):
- _takeable = True
- def _convert_key(self, key, is_setter: bool = False):
- """
- Require integer args. (and convert to label arguments)
- """
- for a, i in zip(self.obj.axes, key):
- if not is_integer(i):
- raise ValueError("iAt based indexing can only have integer indexers")
- return key
- def _tuplify(ndim: int, loc: Hashable) -> Tuple[Union[Hashable, slice], ...]:
- """
- Given an indexer for the first dimension, create an equivalent tuple
- for indexing over all dimensions.
- Parameters
- ----------
- ndim : int
- loc : object
- Returns
- -------
- tuple
- """
- _tup: List[Union[Hashable, slice]]
- _tup = [slice(None, None) for _ in range(ndim)]
- _tup[0] = loc
- return tuple(_tup)
- def convert_to_index_sliceable(obj, key):
- """
- If we are index sliceable, then return my slicer, otherwise return None.
- """
- idx = obj.index
- if isinstance(key, slice):
- return idx._convert_slice_indexer(key, kind="getitem")
- elif isinstance(key, str):
- # we are an actual column
- if key in obj._data.items:
- return None
- # We might have a datetimelike string that we can translate to a
- # slice here via partial string indexing
- if idx._supports_partial_string_indexing:
- try:
- return idx._get_string_slice(key)
- except (KeyError, ValueError, NotImplementedError):
- return None
- return None
- def check_bool_indexer(index: Index, key) -> np.ndarray:
- """
- Check if key is a valid boolean indexer for an object with such index and
- perform reindexing or conversion if needed.
- This function assumes that is_bool_indexer(key) == True.
- Parameters
- ----------
- index : Index
- Index of the object on which the indexing is done.
- key : list-like
- Boolean indexer to check.
- Returns
- -------
- np.array
- Resulting key.
- Raises
- ------
- IndexError
- If the key does not have the same length as index.
- IndexingError
- If the index of the key is unalignable to index.
- """
- result = key
- if isinstance(key, ABCSeries) and not key.index.equals(index):
- result = result.reindex(index)
- mask = isna(result._values)
- if mask.any():
- raise IndexingError(
- "Unalignable boolean Series provided as "
- "indexer (index of the boolean Series and of "
- "the indexed object do not match)."
- )
- result = result.astype(bool)._values
- elif is_object_dtype(key):
- # key might be object-dtype bool, check_array_indexer needs bool array
- result = np.asarray(result, dtype=bool)
- result = check_array_indexer(index, result)
- else:
- result = check_array_indexer(index, result)
- return result
- def convert_missing_indexer(indexer):
- """
- Reverse convert a missing indexer, which is a dict
- return the scalar indexer and a boolean indicating if we converted
- """
- if isinstance(indexer, dict):
- # a missing key (but not a tuple indexer)
- indexer = indexer["key"]
- if isinstance(indexer, bool):
- raise KeyError("cannot use a single bool to index into setitem")
- return indexer, True
- return indexer, False
- def convert_from_missing_indexer_tuple(indexer, axes):
- """
- Create a filtered indexer that doesn't have any missing indexers.
- """
- def get_indexer(_i, _idx):
- return axes[_i].get_loc(_idx["key"]) if isinstance(_idx, dict) else _idx
- return tuple(get_indexer(_i, _idx) for _i, _idx in enumerate(indexer))
- def maybe_convert_ix(*args):
- """
- We likely want to take the cross-product.
- """
- ixify = True
- for arg in args:
- if not isinstance(arg, (np.ndarray, list, ABCSeries, Index)):
- ixify = False
- if ixify:
- return np.ix_(*args)
- else:
- return args
- def is_nested_tuple(tup, labels) -> bool:
- """
- Returns
- -------
- bool
- """
- # check for a compatible nested tuple and multiindexes among the axes
- if not isinstance(tup, tuple):
- return False
- for i, k in enumerate(tup):
- if is_list_like(k) or isinstance(k, slice):
- return isinstance(labels, ABCMultiIndex)
- return False
- def is_label_like(key) -> bool:
- """
- Returns
- -------
- bool
- """
- # select a label or row
- return not isinstance(key, slice) and not is_list_like_indexer(key)
- def need_slice(obj) -> bool:
- """
- Returns
- -------
- bool
- """
- return (
- obj.start is not None
- or obj.stop is not None
- or (obj.step is not None and obj.step != 1)
- )
- def _non_reducing_slice(slice_):
- """
- Ensurse that a slice doesn't reduce to a Series or Scalar.
- Any user-paseed `subset` should have this called on it
- to make sure we're always working with DataFrames.
- """
- # default to column slice, like DataFrame
- # ['A', 'B'] -> IndexSlices[:, ['A', 'B']]
- kinds = (ABCSeries, np.ndarray, Index, list, str)
- if isinstance(slice_, kinds):
- slice_ = IndexSlice[:, slice_]
- def pred(part) -> bool:
- """
- Returns
- -------
- bool
- True if slice does *not* reduce,
- False if `part` is a tuple.
- """
- # true when slice does *not* reduce, False when part is a tuple,
- # i.e. MultiIndex slice
- return (isinstance(part, slice) or is_list_like(part)) and not isinstance(
- part, tuple
- )
- if not is_list_like(slice_):
- if not isinstance(slice_, slice):
- # a 1-d slice, like df.loc[1]
- slice_ = [[slice_]]
- else:
- # slice(a, b, c)
- slice_ = [slice_] # to tuplize later
- else:
- slice_ = [part if pred(part) else [part] for part in slice_]
- return tuple(slice_)
- def _maybe_numeric_slice(df, slice_, include_bool=False):
- """
- Want nice defaults for background_gradient that don't break
- with non-numeric data. But if slice_ is passed go with that.
- """
- if slice_ is None:
- dtypes = [np.number]
- if include_bool:
- dtypes.append(bool)
- slice_ = IndexSlice[:, df.select_dtypes(include=dtypes).columns]
- return slice_
- def _can_do_equal_len(labels, value, plane_indexer, lplane_indexer, obj) -> bool:
- """
- Returns
- -------
- bool
- True if we have an equal len settable.
- """
- if not len(labels) == 1 or not np.iterable(value) or is_scalar(plane_indexer[0]):
- return False
- item = labels[0]
- index = obj[item].index
- values_len = len(value)
- # equal len list/ndarray
- if len(index) == values_len:
- return True
- elif lplane_indexer == values_len:
- return True
- return False
|