style.py 146 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946
  1. """
  2. Module for applying conditional formatting to DataFrames and Series.
  3. """
  4. from __future__ import annotations
  5. from contextlib import contextmanager
  6. import copy
  7. from functools import partial
  8. import operator
  9. from typing import (
  10. TYPE_CHECKING,
  11. Any,
  12. Callable,
  13. Generator,
  14. Hashable,
  15. Sequence,
  16. overload,
  17. )
  18. import numpy as np
  19. from pandas._config import get_option
  20. from pandas._typing import (
  21. Axis,
  22. AxisInt,
  23. FilePath,
  24. IndexLabel,
  25. Level,
  26. QuantileInterpolation,
  27. Scalar,
  28. StorageOptions,
  29. WriteBuffer,
  30. )
  31. from pandas.compat._optional import import_optional_dependency
  32. from pandas.util._decorators import (
  33. Substitution,
  34. doc,
  35. )
  36. import pandas as pd
  37. from pandas import (
  38. IndexSlice,
  39. RangeIndex,
  40. )
  41. import pandas.core.common as com
  42. from pandas.core.frame import (
  43. DataFrame,
  44. Series,
  45. )
  46. from pandas.core.generic import NDFrame
  47. from pandas.core.shared_docs import _shared_docs
  48. from pandas.io.formats.format import save_to_buffer
  49. jinja2 = import_optional_dependency("jinja2", extra="DataFrame.style requires jinja2.")
  50. from pandas.io.formats.style_render import (
  51. CSSProperties,
  52. CSSStyles,
  53. ExtFormatter,
  54. StylerRenderer,
  55. Subset,
  56. Tooltips,
  57. format_table_styles,
  58. maybe_convert_css_to_tuples,
  59. non_reducing_slice,
  60. refactor_levels,
  61. )
  62. if TYPE_CHECKING:
  63. from matplotlib.colors import Colormap
  64. try:
  65. import matplotlib as mpl
  66. import matplotlib.pyplot as plt
  67. has_mpl = True
  68. except ImportError:
  69. has_mpl = False
  70. @contextmanager
  71. def _mpl(func: Callable) -> Generator[tuple[Any, Any], None, None]:
  72. if has_mpl:
  73. yield plt, mpl
  74. else:
  75. raise ImportError(f"{func.__name__} requires matplotlib.")
  76. ####
  77. # Shared Doc Strings
  78. subset_args = """subset : label, array-like, IndexSlice, optional
  79. A valid 2d input to `DataFrame.loc[<subset>]`, or, in the case of a 1d input
  80. or single key, to `DataFrame.loc[:, <subset>]` where the columns are
  81. prioritised, to limit ``data`` to *before* applying the function."""
  82. properties_args = """props : str, default None
  83. CSS properties to use for highlighting. If ``props`` is given, ``color``
  84. is not used."""
  85. coloring_args = """color : str, default '{default}'
  86. Background color to use for highlighting."""
  87. buffering_args = """buf : str, path object, file-like object, optional
  88. String, path object (implementing ``os.PathLike[str]``), or file-like
  89. object implementing a string ``write()`` function. If ``None``, the result is
  90. returned as a string."""
  91. encoding_args = """encoding : str, optional
  92. Character encoding setting for file output (and meta tags if available).
  93. Defaults to ``pandas.options.styler.render.encoding`` value of "utf-8"."""
  94. #
  95. ###
  96. class Styler(StylerRenderer):
  97. r"""
  98. Helps style a DataFrame or Series according to the data with HTML and CSS.
  99. Parameters
  100. ----------
  101. data : Series or DataFrame
  102. Data to be styled - either a Series or DataFrame.
  103. precision : int, optional
  104. Precision to round floats to. If not given defaults to
  105. ``pandas.options.styler.format.precision``.
  106. .. versionchanged:: 1.4.0
  107. table_styles : list-like, default None
  108. List of {selector: (attr, value)} dicts; see Notes.
  109. uuid : str, default None
  110. A unique identifier to avoid CSS collisions; generated automatically.
  111. caption : str, tuple, default None
  112. String caption to attach to the table. Tuple only used for LaTeX dual captions.
  113. table_attributes : str, default None
  114. Items that show up in the opening ``<table>`` tag
  115. in addition to automatic (by default) id.
  116. cell_ids : bool, default True
  117. If True, each cell will have an ``id`` attribute in their HTML tag.
  118. The ``id`` takes the form ``T_<uuid>_row<num_row>_col<num_col>``
  119. where ``<uuid>`` is the unique identifier, ``<num_row>`` is the row
  120. number and ``<num_col>`` is the column number.
  121. na_rep : str, optional
  122. Representation for missing values.
  123. If ``na_rep`` is None, no special formatting is applied, and falls back to
  124. ``pandas.options.styler.format.na_rep``.
  125. uuid_len : int, default 5
  126. If ``uuid`` is not specified, the length of the ``uuid`` to randomly generate
  127. expressed in hex characters, in range [0, 32].
  128. .. versionadded:: 1.2.0
  129. decimal : str, optional
  130. Character used as decimal separator for floats, complex and integers. If not
  131. given uses ``pandas.options.styler.format.decimal``.
  132. .. versionadded:: 1.3.0
  133. thousands : str, optional, default None
  134. Character used as thousands separator for floats, complex and integers. If not
  135. given uses ``pandas.options.styler.format.thousands``.
  136. .. versionadded:: 1.3.0
  137. escape : str, optional
  138. Use 'html' to replace the characters ``&``, ``<``, ``>``, ``'``, and ``"``
  139. in cell display string with HTML-safe sequences.
  140. Use 'latex' to replace the characters ``&``, ``%``, ``$``, ``#``, ``_``,
  141. ``{``, ``}``, ``~``, ``^``, and ``\`` in the cell display string with
  142. LaTeX-safe sequences. If not given uses ``pandas.options.styler.format.escape``.
  143. .. versionadded:: 1.3.0
  144. formatter : str, callable, dict, optional
  145. Object to define how values are displayed. See ``Styler.format``. If not given
  146. uses ``pandas.options.styler.format.formatter``.
  147. .. versionadded:: 1.4.0
  148. Attributes
  149. ----------
  150. env : Jinja2 jinja2.Environment
  151. template_html : Jinja2 Template
  152. template_html_table : Jinja2 Template
  153. template_html_style : Jinja2 Template
  154. template_latex : Jinja2 Template
  155. loader : Jinja2 Loader
  156. See Also
  157. --------
  158. DataFrame.style : Return a Styler object containing methods for building
  159. a styled HTML representation for the DataFrame.
  160. Notes
  161. -----
  162. Most styling will be done by passing style functions into
  163. ``Styler.apply`` or ``Styler.applymap``. Style functions should
  164. return values with strings containing CSS ``'attr: value'`` that will
  165. be applied to the indicated cells.
  166. If using in the Jupyter notebook, Styler has defined a ``_repr_html_``
  167. to automatically render itself. Otherwise call Styler.to_html to get
  168. the generated HTML.
  169. CSS classes are attached to the generated HTML
  170. * Index and Column names include ``index_name`` and ``level<k>``
  171. where `k` is its level in a MultiIndex
  172. * Index label cells include
  173. * ``row_heading``
  174. * ``row<n>`` where `n` is the numeric position of the row
  175. * ``level<k>`` where `k` is the level in a MultiIndex
  176. * Column label cells include
  177. * ``col_heading``
  178. * ``col<n>`` where `n` is the numeric position of the column
  179. * ``level<k>`` where `k` is the level in a MultiIndex
  180. * Blank cells include ``blank``
  181. * Data cells include ``data``
  182. * Trimmed cells include ``col_trim`` or ``row_trim``.
  183. Any, or all, or these classes can be renamed by using the ``css_class_names``
  184. argument in ``Styler.set_table_classes``, giving a value such as
  185. *{"row": "MY_ROW_CLASS", "col_trim": "", "row_trim": ""}*.
  186. """
  187. def __init__(
  188. self,
  189. data: DataFrame | Series,
  190. precision: int | None = None,
  191. table_styles: CSSStyles | None = None,
  192. uuid: str | None = None,
  193. caption: str | tuple | list | None = None,
  194. table_attributes: str | None = None,
  195. cell_ids: bool = True,
  196. na_rep: str | None = None,
  197. uuid_len: int = 5,
  198. decimal: str | None = None,
  199. thousands: str | None = None,
  200. escape: str | None = None,
  201. formatter: ExtFormatter | None = None,
  202. ) -> None:
  203. super().__init__(
  204. data=data,
  205. uuid=uuid,
  206. uuid_len=uuid_len,
  207. table_styles=table_styles,
  208. table_attributes=table_attributes,
  209. caption=caption,
  210. cell_ids=cell_ids,
  211. precision=precision,
  212. )
  213. # validate ordered args
  214. thousands = thousands or get_option("styler.format.thousands")
  215. decimal = decimal or get_option("styler.format.decimal")
  216. na_rep = na_rep or get_option("styler.format.na_rep")
  217. escape = escape or get_option("styler.format.escape")
  218. formatter = formatter or get_option("styler.format.formatter")
  219. # precision is handled by superclass as default for performance
  220. self.format(
  221. formatter=formatter,
  222. precision=precision,
  223. na_rep=na_rep,
  224. escape=escape,
  225. decimal=decimal,
  226. thousands=thousands,
  227. )
  228. def concat(self, other: Styler) -> Styler:
  229. """
  230. Append another Styler to combine the output into a single table.
  231. .. versionadded:: 1.5.0
  232. Parameters
  233. ----------
  234. other : Styler
  235. The other Styler object which has already been styled and formatted. The
  236. data for this Styler must have the same columns as the original, and the
  237. number of index levels must also be the same to render correctly.
  238. Returns
  239. -------
  240. Styler
  241. Notes
  242. -----
  243. The purpose of this method is to extend existing styled dataframes with other
  244. metrics that may be useful but may not conform to the original's structure.
  245. For example adding a sub total row, or displaying metrics such as means,
  246. variance or counts.
  247. Styles that are applied using the ``apply``, ``applymap``, ``apply_index``
  248. and ``applymap_index``, and formatting applied with ``format`` and
  249. ``format_index`` will be preserved.
  250. .. warning::
  251. Only the output methods ``to_html``, ``to_string`` and ``to_latex``
  252. currently work with concatenated Stylers.
  253. Other output methods, including ``to_excel``, **do not** work with
  254. concatenated Stylers.
  255. The following should be noted:
  256. - ``table_styles``, ``table_attributes``, ``caption`` and ``uuid`` are all
  257. inherited from the original Styler and not ``other``.
  258. - hidden columns and hidden index levels will be inherited from the
  259. original Styler
  260. - ``css`` will be inherited from the original Styler, and the value of
  261. keys ``data``, ``row_heading`` and ``row`` will be prepended with
  262. ``foot0_``. If more concats are chained, their styles will be prepended
  263. with ``foot1_``, ''foot_2'', etc., and if a concatenated style have
  264. another concatanated style, the second style will be prepended with
  265. ``foot{parent}_foot{child}_``.
  266. A common use case is to concatenate user defined functions with
  267. ``DataFrame.agg`` or with described statistics via ``DataFrame.describe``.
  268. See examples.
  269. Examples
  270. --------
  271. A common use case is adding totals rows, or otherwise, via methods calculated
  272. in ``DataFrame.agg``.
  273. >>> df = DataFrame([[4, 6], [1, 9], [3, 4], [5, 5], [9,6]],
  274. ... columns=["Mike", "Jim"],
  275. ... index=["Mon", "Tue", "Wed", "Thurs", "Fri"])
  276. >>> styler = df.style.concat(df.agg(["sum"]).style) # doctest: +SKIP
  277. .. figure:: ../../_static/style/footer_simple.png
  278. Since the concatenated object is a Styler the existing functionality can be
  279. used to conditionally format it as well as the original.
  280. >>> descriptors = df.agg(["sum", "mean", lambda s: s.dtype])
  281. >>> descriptors.index = ["Total", "Average", "dtype"]
  282. >>> other = (descriptors.style
  283. ... .highlight_max(axis=1, subset=(["Total", "Average"], slice(None)))
  284. ... .format(subset=("Average", slice(None)), precision=2, decimal=",")
  285. ... .applymap(lambda v: "font-weight: bold;"))
  286. >>> styler = (df.style
  287. ... .highlight_max(color="salmon")
  288. ... .set_table_styles([{"selector": ".foot_row0",
  289. ... "props": "border-top: 1px solid black;"}]))
  290. >>> styler.concat(other) # doctest: +SKIP
  291. .. figure:: ../../_static/style/footer_extended.png
  292. When ``other`` has fewer index levels than the original Styler it is possible
  293. to extend the index in ``other``, with placeholder levels.
  294. >>> df = DataFrame([[1], [2]], index=pd.MultiIndex.from_product([[0], [1, 2]]))
  295. >>> descriptors = df.agg(["sum"])
  296. >>> descriptors.index = pd.MultiIndex.from_product([[""], descriptors.index])
  297. >>> df.style.concat(descriptors.style) # doctest: +SKIP
  298. """
  299. if not isinstance(other, Styler):
  300. raise TypeError("`other` must be of type `Styler`")
  301. if not self.data.columns.equals(other.data.columns):
  302. raise ValueError("`other.data` must have same columns as `Styler.data`")
  303. if not self.data.index.nlevels == other.data.index.nlevels:
  304. raise ValueError(
  305. "number of index levels must be same in `other` "
  306. "as in `Styler`. See documentation for suggestions."
  307. )
  308. self.concatenated.append(other)
  309. return self
  310. def _repr_html_(self) -> str | None:
  311. """
  312. Hooks into Jupyter notebook rich display system, which calls _repr_html_ by
  313. default if an object is returned at the end of a cell.
  314. """
  315. if get_option("styler.render.repr") == "html":
  316. return self.to_html()
  317. return None
  318. def _repr_latex_(self) -> str | None:
  319. if get_option("styler.render.repr") == "latex":
  320. return self.to_latex()
  321. return None
  322. def set_tooltips(
  323. self,
  324. ttips: DataFrame,
  325. props: CSSProperties | None = None,
  326. css_class: str | None = None,
  327. ) -> Styler:
  328. """
  329. Set the DataFrame of strings on ``Styler`` generating ``:hover`` tooltips.
  330. These string based tooltips are only applicable to ``<td>`` HTML elements,
  331. and cannot be used for column or index headers.
  332. .. versionadded:: 1.3.0
  333. Parameters
  334. ----------
  335. ttips : DataFrame
  336. DataFrame containing strings that will be translated to tooltips, mapped
  337. by identical column and index values that must exist on the underlying
  338. Styler data. None, NaN values, and empty strings will be ignored and
  339. not affect the rendered HTML.
  340. props : list-like or str, optional
  341. List of (attr, value) tuples or a valid CSS string. If ``None`` adopts
  342. the internal default values described in notes.
  343. css_class : str, optional
  344. Name of the tooltip class used in CSS, should conform to HTML standards.
  345. Only useful if integrating tooltips with external CSS. If ``None`` uses the
  346. internal default value 'pd-t'.
  347. Returns
  348. -------
  349. Styler
  350. Notes
  351. -----
  352. Tooltips are created by adding `<span class="pd-t"></span>` to each data cell
  353. and then manipulating the table level CSS to attach pseudo hover and pseudo
  354. after selectors to produce the required the results.
  355. The default properties for the tooltip CSS class are:
  356. - visibility: hidden
  357. - position: absolute
  358. - z-index: 1
  359. - background-color: black
  360. - color: white
  361. - transform: translate(-20px, -20px)
  362. The property 'visibility: hidden;' is a key prerequisite to the hover
  363. functionality, and should always be included in any manual properties
  364. specification, using the ``props`` argument.
  365. Tooltips are not designed to be efficient, and can add large amounts of
  366. additional HTML for larger tables, since they also require that ``cell_ids``
  367. is forced to `True`.
  368. Examples
  369. --------
  370. Basic application
  371. >>> df = pd.DataFrame(data=[[0, 1], [2, 3]])
  372. >>> ttips = pd.DataFrame(
  373. ... data=[["Min", ""], [np.nan, "Max"]], columns=df.columns, index=df.index
  374. ... )
  375. >>> s = df.style.set_tooltips(ttips).to_html()
  376. Optionally controlling the tooltip visual display
  377. >>> df.style.set_tooltips(ttips, css_class='tt-add', props=[
  378. ... ('visibility', 'hidden'),
  379. ... ('position', 'absolute'),
  380. ... ('z-index', 1)]) # doctest: +SKIP
  381. >>> df.style.set_tooltips(ttips, css_class='tt-add',
  382. ... props='visibility:hidden; position:absolute; z-index:1;')
  383. ... # doctest: +SKIP
  384. """
  385. if not self.cell_ids:
  386. # tooltips not optimised for individual cell check. requires reasonable
  387. # redesign and more extensive code for a feature that might be rarely used.
  388. raise NotImplementedError(
  389. "Tooltips can only render with 'cell_ids' is True."
  390. )
  391. if not ttips.index.is_unique or not ttips.columns.is_unique:
  392. raise KeyError(
  393. "Tooltips render only if `ttips` has unique index and columns."
  394. )
  395. if self.tooltips is None: # create a default instance if necessary
  396. self.tooltips = Tooltips()
  397. self.tooltips.tt_data = ttips
  398. if props:
  399. self.tooltips.class_properties = props
  400. if css_class:
  401. self.tooltips.class_name = css_class
  402. return self
  403. @doc(
  404. NDFrame.to_excel,
  405. klass="Styler",
  406. storage_options=_shared_docs["storage_options"],
  407. storage_options_versionadded="1.5.0",
  408. )
  409. def to_excel(
  410. self,
  411. excel_writer,
  412. sheet_name: str = "Sheet1",
  413. na_rep: str = "",
  414. float_format: str | None = None,
  415. columns: Sequence[Hashable] | None = None,
  416. header: Sequence[Hashable] | bool = True,
  417. index: bool = True,
  418. index_label: IndexLabel | None = None,
  419. startrow: int = 0,
  420. startcol: int = 0,
  421. engine: str | None = None,
  422. merge_cells: bool = True,
  423. encoding: str | None = None,
  424. inf_rep: str = "inf",
  425. verbose: bool = True,
  426. freeze_panes: tuple[int, int] | None = None,
  427. storage_options: StorageOptions = None,
  428. ) -> None:
  429. from pandas.io.formats.excel import ExcelFormatter
  430. formatter = ExcelFormatter(
  431. self,
  432. na_rep=na_rep,
  433. cols=columns,
  434. header=header,
  435. float_format=float_format,
  436. index=index,
  437. index_label=index_label,
  438. merge_cells=merge_cells,
  439. inf_rep=inf_rep,
  440. )
  441. formatter.write(
  442. excel_writer,
  443. sheet_name=sheet_name,
  444. startrow=startrow,
  445. startcol=startcol,
  446. freeze_panes=freeze_panes,
  447. engine=engine,
  448. storage_options=storage_options,
  449. )
  450. @overload
  451. def to_latex(
  452. self,
  453. buf: FilePath | WriteBuffer[str],
  454. *,
  455. column_format: str | None = ...,
  456. position: str | None = ...,
  457. position_float: str | None = ...,
  458. hrules: bool | None = ...,
  459. clines: str | None = ...,
  460. label: str | None = ...,
  461. caption: str | tuple | None = ...,
  462. sparse_index: bool | None = ...,
  463. sparse_columns: bool | None = ...,
  464. multirow_align: str | None = ...,
  465. multicol_align: str | None = ...,
  466. siunitx: bool = ...,
  467. environment: str | None = ...,
  468. encoding: str | None = ...,
  469. convert_css: bool = ...,
  470. ) -> None:
  471. ...
  472. @overload
  473. def to_latex(
  474. self,
  475. buf: None = ...,
  476. *,
  477. column_format: str | None = ...,
  478. position: str | None = ...,
  479. position_float: str | None = ...,
  480. hrules: bool | None = ...,
  481. clines: str | None = ...,
  482. label: str | None = ...,
  483. caption: str | tuple | None = ...,
  484. sparse_index: bool | None = ...,
  485. sparse_columns: bool | None = ...,
  486. multirow_align: str | None = ...,
  487. multicol_align: str | None = ...,
  488. siunitx: bool = ...,
  489. environment: str | None = ...,
  490. encoding: str | None = ...,
  491. convert_css: bool = ...,
  492. ) -> str:
  493. ...
  494. def to_latex(
  495. self,
  496. buf: FilePath | WriteBuffer[str] | None = None,
  497. *,
  498. column_format: str | None = None,
  499. position: str | None = None,
  500. position_float: str | None = None,
  501. hrules: bool | None = None,
  502. clines: str | None = None,
  503. label: str | None = None,
  504. caption: str | tuple | None = None,
  505. sparse_index: bool | None = None,
  506. sparse_columns: bool | None = None,
  507. multirow_align: str | None = None,
  508. multicol_align: str | None = None,
  509. siunitx: bool = False,
  510. environment: str | None = None,
  511. encoding: str | None = None,
  512. convert_css: bool = False,
  513. ) -> str | None:
  514. r"""
  515. Write Styler to a file, buffer or string in LaTeX format.
  516. .. versionadded:: 1.3.0
  517. Parameters
  518. ----------
  519. buf : str, path object, file-like object, or None, default None
  520. String, path object (implementing ``os.PathLike[str]``), or file-like
  521. object implementing a string ``write()`` function. If None, the result is
  522. returned as a string.
  523. column_format : str, optional
  524. The LaTeX column specification placed in location:
  525. \\begin{tabular}{<column_format>}
  526. Defaults to 'l' for index and
  527. non-numeric data columns, and, for numeric data columns,
  528. to 'r' by default, or 'S' if ``siunitx`` is ``True``.
  529. position : str, optional
  530. The LaTeX positional argument (e.g. 'h!') for tables, placed in location:
  531. ``\\begin{table}[<position>]``.
  532. position_float : {"centering", "raggedleft", "raggedright"}, optional
  533. The LaTeX float command placed in location:
  534. \\begin{table}[<position>]
  535. \\<position_float>
  536. Cannot be used if ``environment`` is "longtable".
  537. hrules : bool
  538. Set to `True` to add \\toprule, \\midrule and \\bottomrule from the
  539. {booktabs} LaTeX package.
  540. Defaults to ``pandas.options.styler.latex.hrules``, which is `False`.
  541. .. versionchanged:: 1.4.0
  542. clines : str, optional
  543. Use to control adding \\cline commands for the index labels separation.
  544. Possible values are:
  545. - `None`: no cline commands are added (default).
  546. - `"all;data"`: a cline is added for every index value extending the
  547. width of the table, including data entries.
  548. - `"all;index"`: as above with lines extending only the width of the
  549. index entries.
  550. - `"skip-last;data"`: a cline is added for each index value except the
  551. last level (which is never sparsified), extending the widtn of the
  552. table.
  553. - `"skip-last;index"`: as above with lines extending only the width of the
  554. index entries.
  555. .. versionadded:: 1.4.0
  556. label : str, optional
  557. The LaTeX label included as: \\label{<label>}.
  558. This is used with \\ref{<label>} in the main .tex file.
  559. caption : str, tuple, optional
  560. If string, the LaTeX table caption included as: \\caption{<caption>}.
  561. If tuple, i.e ("full caption", "short caption"), the caption included
  562. as: \\caption[<caption[1]>]{<caption[0]>}.
  563. sparse_index : bool, optional
  564. Whether to sparsify the display of a hierarchical index. Setting to False
  565. will display each explicit level element in a hierarchical key for each row.
  566. Defaults to ``pandas.options.styler.sparse.index``, which is `True`.
  567. sparse_columns : bool, optional
  568. Whether to sparsify the display of a hierarchical index. Setting to False
  569. will display each explicit level element in a hierarchical key for each
  570. column. Defaults to ``pandas.options.styler.sparse.columns``, which
  571. is `True`.
  572. multirow_align : {"c", "t", "b", "naive"}, optional
  573. If sparsifying hierarchical MultiIndexes whether to align text centrally,
  574. at the top or bottom using the multirow package. If not given defaults to
  575. ``pandas.options.styler.latex.multirow_align``, which is `"c"`.
  576. If "naive" is given renders without multirow.
  577. .. versionchanged:: 1.4.0
  578. multicol_align : {"r", "c", "l", "naive-l", "naive-r"}, optional
  579. If sparsifying hierarchical MultiIndex columns whether to align text at
  580. the left, centrally, or at the right. If not given defaults to
  581. ``pandas.options.styler.latex.multicol_align``, which is "r".
  582. If a naive option is given renders without multicol.
  583. Pipe decorators can also be added to non-naive values to draw vertical
  584. rules, e.g. "\|r" will draw a rule on the left side of right aligned merged
  585. cells.
  586. .. versionchanged:: 1.4.0
  587. siunitx : bool, default False
  588. Set to ``True`` to structure LaTeX compatible with the {siunitx} package.
  589. environment : str, optional
  590. If given, the environment that will replace 'table' in ``\\begin{table}``.
  591. If 'longtable' is specified then a more suitable template is
  592. rendered. If not given defaults to
  593. ``pandas.options.styler.latex.environment``, which is `None`.
  594. .. versionadded:: 1.4.0
  595. encoding : str, optional
  596. Character encoding setting. Defaults
  597. to ``pandas.options.styler.render.encoding``, which is "utf-8".
  598. convert_css : bool, default False
  599. Convert simple cell-styles from CSS to LaTeX format. Any CSS not found in
  600. conversion table is dropped. A style can be forced by adding option
  601. `--latex`. See notes.
  602. Returns
  603. -------
  604. str or None
  605. If `buf` is None, returns the result as a string. Otherwise returns `None`.
  606. See Also
  607. --------
  608. Styler.format: Format the text display value of cells.
  609. Notes
  610. -----
  611. **Latex Packages**
  612. For the following features we recommend the following LaTeX inclusions:
  613. ===================== ==========================================================
  614. Feature Inclusion
  615. ===================== ==========================================================
  616. sparse columns none: included within default {tabular} environment
  617. sparse rows \\usepackage{multirow}
  618. hrules \\usepackage{booktabs}
  619. colors \\usepackage[table]{xcolor}
  620. siunitx \\usepackage{siunitx}
  621. bold (with siunitx) | \\usepackage{etoolbox}
  622. | \\robustify\\bfseries
  623. | \\sisetup{detect-all = true} *(within {document})*
  624. italic (with siunitx) | \\usepackage{etoolbox}
  625. | \\robustify\\itshape
  626. | \\sisetup{detect-all = true} *(within {document})*
  627. environment \\usepackage{longtable} if arg is "longtable"
  628. | or any other relevant environment package
  629. hyperlinks \\usepackage{hyperref}
  630. ===================== ==========================================================
  631. **Cell Styles**
  632. LaTeX styling can only be rendered if the accompanying styling functions have
  633. been constructed with appropriate LaTeX commands. All styling
  634. functionality is built around the concept of a CSS ``(<attribute>, <value>)``
  635. pair (see `Table Visualization <../../user_guide/style.ipynb>`_), and this
  636. should be replaced by a LaTeX
  637. ``(<command>, <options>)`` approach. Each cell will be styled individually
  638. using nested LaTeX commands with their accompanied options.
  639. For example the following code will highlight and bold a cell in HTML-CSS:
  640. >>> df = pd.DataFrame([[1,2], [3,4]])
  641. >>> s = df.style.highlight_max(axis=None,
  642. ... props='background-color:red; font-weight:bold;')
  643. >>> s.to_html() # doctest: +SKIP
  644. The equivalent using LaTeX only commands is the following:
  645. >>> s = df.style.highlight_max(axis=None,
  646. ... props='cellcolor:{red}; bfseries: ;')
  647. >>> s.to_latex() # doctest: +SKIP
  648. Internally these structured LaTeX ``(<command>, <options>)`` pairs
  649. are translated to the
  650. ``display_value`` with the default structure:
  651. ``\<command><options> <display_value>``.
  652. Where there are multiple commands the latter is nested recursively, so that
  653. the above example highlighted cell is rendered as
  654. ``\cellcolor{red} \bfseries 4``.
  655. Occasionally this format does not suit the applied command, or
  656. combination of LaTeX packages that is in use, so additional flags can be
  657. added to the ``<options>``, within the tuple, to result in different
  658. positions of required braces (the **default** being the same as ``--nowrap``):
  659. =================================== ============================================
  660. Tuple Format Output Structure
  661. =================================== ============================================
  662. (<command>,<options>) \\<command><options> <display_value>
  663. (<command>,<options> ``--nowrap``) \\<command><options> <display_value>
  664. (<command>,<options> ``--rwrap``) \\<command><options>{<display_value>}
  665. (<command>,<options> ``--wrap``) {\\<command><options> <display_value>}
  666. (<command>,<options> ``--lwrap``) {\\<command><options>} <display_value>
  667. (<command>,<options> ``--dwrap``) {\\<command><options>}{<display_value>}
  668. =================================== ============================================
  669. For example the `textbf` command for font-weight
  670. should always be used with `--rwrap` so ``('textbf', '--rwrap')`` will render a
  671. working cell, wrapped with braces, as ``\textbf{<display_value>}``.
  672. A more comprehensive example is as follows:
  673. >>> df = pd.DataFrame([[1, 2.2, "dogs"], [3, 4.4, "cats"], [2, 6.6, "cows"]],
  674. ... index=["ix1", "ix2", "ix3"],
  675. ... columns=["Integers", "Floats", "Strings"])
  676. >>> s = df.style.highlight_max(
  677. ... props='cellcolor:[HTML]{FFFF00}; color:{red};'
  678. ... 'textit:--rwrap; textbf:--rwrap;'
  679. ... )
  680. >>> s.to_latex() # doctest: +SKIP
  681. .. figure:: ../../_static/style/latex_1.png
  682. **Table Styles**
  683. Internally Styler uses its ``table_styles`` object to parse the
  684. ``column_format``, ``position``, ``position_float``, and ``label``
  685. input arguments. These arguments are added to table styles in the format:
  686. .. code-block:: python
  687. set_table_styles([
  688. {"selector": "column_format", "props": f":{column_format};"},
  689. {"selector": "position", "props": f":{position};"},
  690. {"selector": "position_float", "props": f":{position_float};"},
  691. {"selector": "label", "props": f":{{{label.replace(':','§')}}};"}
  692. ], overwrite=False)
  693. Exception is made for the ``hrules`` argument which, in fact, controls all three
  694. commands: ``toprule``, ``bottomrule`` and ``midrule`` simultaneously. Instead of
  695. setting ``hrules`` to ``True``, it is also possible to set each
  696. individual rule definition, by manually setting the ``table_styles``,
  697. for example below we set a regular ``toprule``, set an ``hline`` for
  698. ``bottomrule`` and exclude the ``midrule``:
  699. .. code-block:: python
  700. set_table_styles([
  701. {'selector': 'toprule', 'props': ':toprule;'},
  702. {'selector': 'bottomrule', 'props': ':hline;'},
  703. ], overwrite=False)
  704. If other ``commands`` are added to table styles they will be detected, and
  705. positioned immediately above the '\\begin{tabular}' command. For example to
  706. add odd and even row coloring, from the {colortbl} package, in format
  707. ``\rowcolors{1}{pink}{red}``, use:
  708. .. code-block:: python
  709. set_table_styles([
  710. {'selector': 'rowcolors', 'props': ':{1}{pink}{red};'}
  711. ], overwrite=False)
  712. A more comprehensive example using these arguments is as follows:
  713. >>> df.columns = pd.MultiIndex.from_tuples([
  714. ... ("Numeric", "Integers"),
  715. ... ("Numeric", "Floats"),
  716. ... ("Non-Numeric", "Strings")
  717. ... ])
  718. >>> df.index = pd.MultiIndex.from_tuples([
  719. ... ("L0", "ix1"), ("L0", "ix2"), ("L1", "ix3")
  720. ... ])
  721. >>> s = df.style.highlight_max(
  722. ... props='cellcolor:[HTML]{FFFF00}; color:{red}; itshape:; bfseries:;'
  723. ... )
  724. >>> s.to_latex(
  725. ... column_format="rrrrr", position="h", position_float="centering",
  726. ... hrules=True, label="table:5", caption="Styled LaTeX Table",
  727. ... multirow_align="t", multicol_align="r"
  728. ... ) # doctest: +SKIP
  729. .. figure:: ../../_static/style/latex_2.png
  730. **Formatting**
  731. To format values :meth:`Styler.format` should be used prior to calling
  732. `Styler.to_latex`, as well as other methods such as :meth:`Styler.hide`
  733. for example:
  734. >>> s.clear()
  735. >>> s.table_styles = []
  736. >>> s.caption = None
  737. >>> s.format({
  738. ... ("Numeric", "Integers"): '\${}',
  739. ... ("Numeric", "Floats"): '{:.3f}',
  740. ... ("Non-Numeric", "Strings"): str.upper
  741. ... }) # doctest: +SKIP
  742. Numeric Non-Numeric
  743. Integers Floats Strings
  744. L0 ix1 $1 2.200 DOGS
  745. ix2 $3 4.400 CATS
  746. L1 ix3 $2 6.600 COWS
  747. >>> s.to_latex() # doctest: +SKIP
  748. \begin{tabular}{llrrl}
  749. {} & {} & \multicolumn{2}{r}{Numeric} & {Non-Numeric} \\
  750. {} & {} & {Integers} & {Floats} & {Strings} \\
  751. \multirow[c]{2}{*}{L0} & ix1 & \\$1 & 2.200 & DOGS \\
  752. & ix2 & \$3 & 4.400 & CATS \\
  753. L1 & ix3 & \$2 & 6.600 & COWS \\
  754. \end{tabular}
  755. **CSS Conversion**
  756. This method can convert a Styler constructured with HTML-CSS to LaTeX using
  757. the following limited conversions.
  758. ================== ==================== ============= ==========================
  759. CSS Attribute CSS value LaTeX Command LaTeX Options
  760. ================== ==================== ============= ==========================
  761. font-weight | bold | bfseries
  762. | bolder | bfseries
  763. font-style | italic | itshape
  764. | oblique | slshape
  765. background-color | red cellcolor | {red}--lwrap
  766. | #fe01ea | [HTML]{FE01EA}--lwrap
  767. | #f0e | [HTML]{FF00EE}--lwrap
  768. | rgb(128,255,0) | [rgb]{0.5,1,0}--lwrap
  769. | rgba(128,0,0,0.5) | [rgb]{0.5,0,0}--lwrap
  770. | rgb(25%,255,50%) | [rgb]{0.25,1,0.5}--lwrap
  771. color | red color | {red}
  772. | #fe01ea | [HTML]{FE01EA}
  773. | #f0e | [HTML]{FF00EE}
  774. | rgb(128,255,0) | [rgb]{0.5,1,0}
  775. | rgba(128,0,0,0.5) | [rgb]{0.5,0,0}
  776. | rgb(25%,255,50%) | [rgb]{0.25,1,0.5}
  777. ================== ==================== ============= ==========================
  778. It is also possible to add user-defined LaTeX only styles to a HTML-CSS Styler
  779. using the ``--latex`` flag, and to add LaTeX parsing options that the
  780. converter will detect within a CSS-comment.
  781. >>> df = pd.DataFrame([[1]])
  782. >>> df.style.set_properties(
  783. ... **{"font-weight": "bold /* --dwrap */", "Huge": "--latex--rwrap"}
  784. ... ).to_latex(convert_css=True) # doctest: +SKIP
  785. \begin{tabular}{lr}
  786. {} & {0} \\
  787. 0 & {\bfseries}{\Huge{1}} \\
  788. \end{tabular}
  789. Examples
  790. --------
  791. Below we give a complete step by step example adding some advanced features
  792. and noting some common gotchas.
  793. First we create the DataFrame and Styler as usual, including MultiIndex rows
  794. and columns, which allow for more advanced formatting options:
  795. >>> cidx = pd.MultiIndex.from_arrays([
  796. ... ["Equity", "Equity", "Equity", "Equity",
  797. ... "Stats", "Stats", "Stats", "Stats", "Rating"],
  798. ... ["Energy", "Energy", "Consumer", "Consumer", "", "", "", "", ""],
  799. ... ["BP", "Shell", "H&M", "Unilever",
  800. ... "Std Dev", "Variance", "52w High", "52w Low", ""]
  801. ... ])
  802. >>> iidx = pd.MultiIndex.from_arrays([
  803. ... ["Equity", "Equity", "Equity", "Equity"],
  804. ... ["Energy", "Energy", "Consumer", "Consumer"],
  805. ... ["BP", "Shell", "H&M", "Unilever"]
  806. ... ])
  807. >>> styler = pd.DataFrame([
  808. ... [1, 0.8, 0.66, 0.72, 32.1678, 32.1678**2, 335.12, 240.89, "Buy"],
  809. ... [0.8, 1.0, 0.69, 0.79, 1.876, 1.876**2, 14.12, 19.78, "Hold"],
  810. ... [0.66, 0.69, 1.0, 0.86, 7, 7**2, 210.9, 140.6, "Buy"],
  811. ... [0.72, 0.79, 0.86, 1.0, 213.76, 213.76**2, 2807, 3678, "Sell"],
  812. ... ], columns=cidx, index=iidx).style
  813. Second we will format the display and, since our table is quite wide, will
  814. hide the repeated level-0 of the index:
  815. >>> (styler.format(subset="Equity", precision=2)
  816. ... .format(subset="Stats", precision=1, thousands=",")
  817. ... .format(subset="Rating", formatter=str.upper)
  818. ... .format_index(escape="latex", axis=1)
  819. ... .format_index(escape="latex", axis=0)
  820. ... .hide(level=0, axis=0)) # doctest: +SKIP
  821. Note that one of the string entries of the index and column headers is "H&M".
  822. Without applying the `escape="latex"` option to the `format_index` method the
  823. resultant LaTeX will fail to render, and the error returned is quite
  824. difficult to debug. Using the appropriate escape the "&" is converted to "\\&".
  825. Thirdly we will apply some (CSS-HTML) styles to our object. We will use a
  826. builtin method and also define our own method to highlight the stock
  827. recommendation:
  828. >>> def rating_color(v):
  829. ... if v == "Buy": color = "#33ff85"
  830. ... elif v == "Sell": color = "#ff5933"
  831. ... else: color = "#ffdd33"
  832. ... return f"color: {color}; font-weight: bold;"
  833. >>> (styler.background_gradient(cmap="inferno", subset="Equity", vmin=0, vmax=1)
  834. ... .applymap(rating_color, subset="Rating")) # doctest: +SKIP
  835. All the above styles will work with HTML (see below) and LaTeX upon conversion:
  836. .. figure:: ../../_static/style/latex_stocks_html.png
  837. However, we finally want to add one LaTeX only style
  838. (from the {graphicx} package), that is not easy to convert from CSS and
  839. pandas does not support it. Notice the `--latex` flag used here,
  840. as well as `--rwrap` to ensure this is formatted correctly and
  841. not ignored upon conversion.
  842. >>> styler.applymap_index(
  843. ... lambda v: "rotatebox:{45}--rwrap--latex;", level=2, axis=1
  844. ... ) # doctest: +SKIP
  845. Finally we render our LaTeX adding in other options as required:
  846. >>> styler.to_latex(
  847. ... caption="Selected stock correlation and simple statistics.",
  848. ... clines="skip-last;data",
  849. ... convert_css=True,
  850. ... position_float="centering",
  851. ... multicol_align="|c|",
  852. ... hrules=True,
  853. ... ) # doctest: +SKIP
  854. \begin{table}
  855. \centering
  856. \caption{Selected stock correlation and simple statistics.}
  857. \begin{tabular}{llrrrrrrrrl}
  858. \toprule
  859. & & \multicolumn{4}{|c|}{Equity} & \multicolumn{4}{|c|}{Stats} & Rating \\
  860. & & \multicolumn{2}{|c|}{Energy} & \multicolumn{2}{|c|}{Consumer} &
  861. \multicolumn{4}{|c|}{} & \\
  862. & & \rotatebox{45}{BP} & \rotatebox{45}{Shell} & \rotatebox{45}{H\&M} &
  863. \rotatebox{45}{Unilever} & \rotatebox{45}{Std Dev} & \rotatebox{45}{Variance} &
  864. \rotatebox{45}{52w High} & \rotatebox{45}{52w Low} & \rotatebox{45}{} \\
  865. \midrule
  866. \multirow[c]{2}{*}{Energy} & BP & {\cellcolor[HTML]{FCFFA4}}
  867. \color[HTML]{000000} 1.00 & {\cellcolor[HTML]{FCA50A}} \color[HTML]{000000}
  868. 0.80 & {\cellcolor[HTML]{EB6628}} \color[HTML]{F1F1F1} 0.66 &
  869. {\cellcolor[HTML]{F68013}} \color[HTML]{F1F1F1} 0.72 & 32.2 & 1,034.8 & 335.1
  870. & 240.9 & \color[HTML]{33FF85} \bfseries BUY \\
  871. & Shell & {\cellcolor[HTML]{FCA50A}} \color[HTML]{000000} 0.80 &
  872. {\cellcolor[HTML]{FCFFA4}} \color[HTML]{000000} 1.00 &
  873. {\cellcolor[HTML]{F1731D}} \color[HTML]{F1F1F1} 0.69 &
  874. {\cellcolor[HTML]{FCA108}} \color[HTML]{000000} 0.79 & 1.9 & 3.5 & 14.1 &
  875. 19.8 & \color[HTML]{FFDD33} \bfseries HOLD \\
  876. \cline{1-11}
  877. \multirow[c]{2}{*}{Consumer} & H\&M & {\cellcolor[HTML]{EB6628}}
  878. \color[HTML]{F1F1F1} 0.66 & {\cellcolor[HTML]{F1731D}} \color[HTML]{F1F1F1}
  879. 0.69 & {\cellcolor[HTML]{FCFFA4}} \color[HTML]{000000} 1.00 &
  880. {\cellcolor[HTML]{FAC42A}} \color[HTML]{000000} 0.86 & 7.0 & 49.0 & 210.9 &
  881. 140.6 & \color[HTML]{33FF85} \bfseries BUY \\
  882. & Unilever & {\cellcolor[HTML]{F68013}} \color[HTML]{F1F1F1} 0.72 &
  883. {\cellcolor[HTML]{FCA108}} \color[HTML]{000000} 0.79 &
  884. {\cellcolor[HTML]{FAC42A}} \color[HTML]{000000} 0.86 &
  885. {\cellcolor[HTML]{FCFFA4}} \color[HTML]{000000} 1.00 & 213.8 & 45,693.3 &
  886. 2,807.0 & 3,678.0 & \color[HTML]{FF5933} \bfseries SELL \\
  887. \cline{1-11}
  888. \bottomrule
  889. \end{tabular}
  890. \end{table}
  891. .. figure:: ../../_static/style/latex_stocks.png
  892. """
  893. obj = self._copy(deepcopy=True) # manipulate table_styles on obj, not self
  894. table_selectors = (
  895. [style["selector"] for style in self.table_styles]
  896. if self.table_styles is not None
  897. else []
  898. )
  899. if column_format is not None:
  900. # add more recent setting to table_styles
  901. obj.set_table_styles(
  902. [{"selector": "column_format", "props": f":{column_format}"}],
  903. overwrite=False,
  904. )
  905. elif "column_format" in table_selectors:
  906. pass # adopt what has been previously set in table_styles
  907. else:
  908. # create a default: set float, complex, int cols to 'r' ('S'), index to 'l'
  909. _original_columns = self.data.columns
  910. self.data.columns = RangeIndex(stop=len(self.data.columns))
  911. numeric_cols = self.data._get_numeric_data().columns.to_list()
  912. self.data.columns = _original_columns
  913. column_format = ""
  914. for level in range(self.index.nlevels):
  915. column_format += "" if self.hide_index_[level] else "l"
  916. for ci, _ in enumerate(self.data.columns):
  917. if ci not in self.hidden_columns:
  918. column_format += (
  919. ("r" if not siunitx else "S") if ci in numeric_cols else "l"
  920. )
  921. obj.set_table_styles(
  922. [{"selector": "column_format", "props": f":{column_format}"}],
  923. overwrite=False,
  924. )
  925. if position:
  926. obj.set_table_styles(
  927. [{"selector": "position", "props": f":{position}"}],
  928. overwrite=False,
  929. )
  930. if position_float:
  931. if environment == "longtable":
  932. raise ValueError(
  933. "`position_float` cannot be used in 'longtable' `environment`"
  934. )
  935. if position_float not in ["raggedright", "raggedleft", "centering"]:
  936. raise ValueError(
  937. f"`position_float` should be one of "
  938. f"'raggedright', 'raggedleft', 'centering', "
  939. f"got: '{position_float}'"
  940. )
  941. obj.set_table_styles(
  942. [{"selector": "position_float", "props": f":{position_float}"}],
  943. overwrite=False,
  944. )
  945. hrules = get_option("styler.latex.hrules") if hrules is None else hrules
  946. if hrules:
  947. obj.set_table_styles(
  948. [
  949. {"selector": "toprule", "props": ":toprule"},
  950. {"selector": "midrule", "props": ":midrule"},
  951. {"selector": "bottomrule", "props": ":bottomrule"},
  952. ],
  953. overwrite=False,
  954. )
  955. if label:
  956. obj.set_table_styles(
  957. [{"selector": "label", "props": f":{{{label.replace(':', '§')}}}"}],
  958. overwrite=False,
  959. )
  960. if caption:
  961. obj.set_caption(caption)
  962. if sparse_index is None:
  963. sparse_index = get_option("styler.sparse.index")
  964. if sparse_columns is None:
  965. sparse_columns = get_option("styler.sparse.columns")
  966. environment = environment or get_option("styler.latex.environment")
  967. multicol_align = multicol_align or get_option("styler.latex.multicol_align")
  968. multirow_align = multirow_align or get_option("styler.latex.multirow_align")
  969. latex = obj._render_latex(
  970. sparse_index=sparse_index,
  971. sparse_columns=sparse_columns,
  972. multirow_align=multirow_align,
  973. multicol_align=multicol_align,
  974. environment=environment,
  975. convert_css=convert_css,
  976. siunitx=siunitx,
  977. clines=clines,
  978. )
  979. encoding = (
  980. (encoding or get_option("styler.render.encoding"))
  981. if isinstance(buf, str) # i.e. a filepath
  982. else encoding
  983. )
  984. return save_to_buffer(latex, buf=buf, encoding=encoding)
  985. @overload
  986. def to_html(
  987. self,
  988. buf: FilePath | WriteBuffer[str],
  989. *,
  990. table_uuid: str | None = ...,
  991. table_attributes: str | None = ...,
  992. sparse_index: bool | None = ...,
  993. sparse_columns: bool | None = ...,
  994. bold_headers: bool = ...,
  995. caption: str | None = ...,
  996. max_rows: int | None = ...,
  997. max_columns: int | None = ...,
  998. encoding: str | None = ...,
  999. doctype_html: bool = ...,
  1000. exclude_styles: bool = ...,
  1001. **kwargs,
  1002. ) -> None:
  1003. ...
  1004. @overload
  1005. def to_html(
  1006. self,
  1007. buf: None = ...,
  1008. *,
  1009. table_uuid: str | None = ...,
  1010. table_attributes: str | None = ...,
  1011. sparse_index: bool | None = ...,
  1012. sparse_columns: bool | None = ...,
  1013. bold_headers: bool = ...,
  1014. caption: str | None = ...,
  1015. max_rows: int | None = ...,
  1016. max_columns: int | None = ...,
  1017. encoding: str | None = ...,
  1018. doctype_html: bool = ...,
  1019. exclude_styles: bool = ...,
  1020. **kwargs,
  1021. ) -> str:
  1022. ...
  1023. @Substitution(buf=buffering_args, encoding=encoding_args)
  1024. def to_html(
  1025. self,
  1026. buf: FilePath | WriteBuffer[str] | None = None,
  1027. *,
  1028. table_uuid: str | None = None,
  1029. table_attributes: str | None = None,
  1030. sparse_index: bool | None = None,
  1031. sparse_columns: bool | None = None,
  1032. bold_headers: bool = False,
  1033. caption: str | None = None,
  1034. max_rows: int | None = None,
  1035. max_columns: int | None = None,
  1036. encoding: str | None = None,
  1037. doctype_html: bool = False,
  1038. exclude_styles: bool = False,
  1039. **kwargs,
  1040. ) -> str | None:
  1041. """
  1042. Write Styler to a file, buffer or string in HTML-CSS format.
  1043. .. versionadded:: 1.3.0
  1044. Parameters
  1045. ----------
  1046. %(buf)s
  1047. table_uuid : str, optional
  1048. Id attribute assigned to the <table> HTML element in the format:
  1049. ``<table id="T_<table_uuid>" ..>``
  1050. If not given uses Styler's initially assigned value.
  1051. table_attributes : str, optional
  1052. Attributes to assign within the `<table>` HTML element in the format:
  1053. ``<table .. <table_attributes> >``
  1054. If not given defaults to Styler's preexisting value.
  1055. sparse_index : bool, optional
  1056. Whether to sparsify the display of a hierarchical index. Setting to False
  1057. will display each explicit level element in a hierarchical key for each row.
  1058. Defaults to ``pandas.options.styler.sparse.index`` value.
  1059. .. versionadded:: 1.4.0
  1060. sparse_columns : bool, optional
  1061. Whether to sparsify the display of a hierarchical index. Setting to False
  1062. will display each explicit level element in a hierarchical key for each
  1063. column. Defaults to ``pandas.options.styler.sparse.columns`` value.
  1064. .. versionadded:: 1.4.0
  1065. bold_headers : bool, optional
  1066. Adds "font-weight: bold;" as a CSS property to table style header cells.
  1067. .. versionadded:: 1.4.0
  1068. caption : str, optional
  1069. Set, or overwrite, the caption on Styler before rendering.
  1070. .. versionadded:: 1.4.0
  1071. max_rows : int, optional
  1072. The maximum number of rows that will be rendered. Defaults to
  1073. ``pandas.options.styler.render.max_rows/max_columns``.
  1074. .. versionadded:: 1.4.0
  1075. max_columns : int, optional
  1076. The maximum number of columns that will be rendered. Defaults to
  1077. ``pandas.options.styler.render.max_columns``, which is None.
  1078. Rows and columns may be reduced if the number of total elements is
  1079. large. This value is set to ``pandas.options.styler.render.max_elements``,
  1080. which is 262144 (18 bit browser rendering).
  1081. .. versionadded:: 1.4.0
  1082. %(encoding)s
  1083. doctype_html : bool, default False
  1084. Whether to output a fully structured HTML file including all
  1085. HTML elements, or just the core ``<style>`` and ``<table>`` elements.
  1086. exclude_styles : bool, default False
  1087. Whether to include the ``<style>`` element and all associated element
  1088. ``class`` and ``id`` identifiers, or solely the ``<table>`` element without
  1089. styling identifiers.
  1090. **kwargs
  1091. Any additional keyword arguments are passed through to the jinja2
  1092. ``self.template.render`` process. This is useful when you need to provide
  1093. additional variables for a custom template.
  1094. Returns
  1095. -------
  1096. str or None
  1097. If `buf` is None, returns the result as a string. Otherwise returns `None`.
  1098. See Also
  1099. --------
  1100. DataFrame.to_html: Write a DataFrame to a file, buffer or string in HTML format.
  1101. """
  1102. obj = self._copy(deepcopy=True) # manipulate table_styles on obj, not self
  1103. if table_uuid:
  1104. obj.set_uuid(table_uuid)
  1105. if table_attributes:
  1106. obj.set_table_attributes(table_attributes)
  1107. if sparse_index is None:
  1108. sparse_index = get_option("styler.sparse.index")
  1109. if sparse_columns is None:
  1110. sparse_columns = get_option("styler.sparse.columns")
  1111. if bold_headers:
  1112. obj.set_table_styles(
  1113. [{"selector": "th", "props": "font-weight: bold;"}], overwrite=False
  1114. )
  1115. if caption is not None:
  1116. obj.set_caption(caption)
  1117. # Build HTML string..
  1118. html = obj._render_html(
  1119. sparse_index=sparse_index,
  1120. sparse_columns=sparse_columns,
  1121. max_rows=max_rows,
  1122. max_cols=max_columns,
  1123. exclude_styles=exclude_styles,
  1124. encoding=encoding or get_option("styler.render.encoding"),
  1125. doctype_html=doctype_html,
  1126. **kwargs,
  1127. )
  1128. return save_to_buffer(
  1129. html, buf=buf, encoding=(encoding if buf is not None else None)
  1130. )
  1131. @overload
  1132. def to_string(
  1133. self,
  1134. buf: FilePath | WriteBuffer[str],
  1135. *,
  1136. encoding=...,
  1137. sparse_index: bool | None = ...,
  1138. sparse_columns: bool | None = ...,
  1139. max_rows: int | None = ...,
  1140. max_columns: int | None = ...,
  1141. delimiter: str = ...,
  1142. ) -> None:
  1143. ...
  1144. @overload
  1145. def to_string(
  1146. self,
  1147. buf: None = ...,
  1148. *,
  1149. encoding=...,
  1150. sparse_index: bool | None = ...,
  1151. sparse_columns: bool | None = ...,
  1152. max_rows: int | None = ...,
  1153. max_columns: int | None = ...,
  1154. delimiter: str = ...,
  1155. ) -> str:
  1156. ...
  1157. @Substitution(buf=buffering_args, encoding=encoding_args)
  1158. def to_string(
  1159. self,
  1160. buf: FilePath | WriteBuffer[str] | None = None,
  1161. *,
  1162. encoding=None,
  1163. sparse_index: bool | None = None,
  1164. sparse_columns: bool | None = None,
  1165. max_rows: int | None = None,
  1166. max_columns: int | None = None,
  1167. delimiter: str = " ",
  1168. ) -> str | None:
  1169. """
  1170. Write Styler to a file, buffer or string in text format.
  1171. .. versionadded:: 1.5.0
  1172. Parameters
  1173. ----------
  1174. %(buf)s
  1175. %(encoding)s
  1176. sparse_index : bool, optional
  1177. Whether to sparsify the display of a hierarchical index. Setting to False
  1178. will display each explicit level element in a hierarchical key for each row.
  1179. Defaults to ``pandas.options.styler.sparse.index`` value.
  1180. sparse_columns : bool, optional
  1181. Whether to sparsify the display of a hierarchical index. Setting to False
  1182. will display each explicit level element in a hierarchical key for each
  1183. column. Defaults to ``pandas.options.styler.sparse.columns`` value.
  1184. max_rows : int, optional
  1185. The maximum number of rows that will be rendered. Defaults to
  1186. ``pandas.options.styler.render.max_rows``, which is None.
  1187. max_columns : int, optional
  1188. The maximum number of columns that will be rendered. Defaults to
  1189. ``pandas.options.styler.render.max_columns``, which is None.
  1190. Rows and columns may be reduced if the number of total elements is
  1191. large. This value is set to ``pandas.options.styler.render.max_elements``,
  1192. which is 262144 (18 bit browser rendering).
  1193. delimiter : str, default single space
  1194. The separator between data elements.
  1195. Returns
  1196. -------
  1197. str or None
  1198. If `buf` is None, returns the result as a string. Otherwise returns `None`.
  1199. """
  1200. obj = self._copy(deepcopy=True)
  1201. if sparse_index is None:
  1202. sparse_index = get_option("styler.sparse.index")
  1203. if sparse_columns is None:
  1204. sparse_columns = get_option("styler.sparse.columns")
  1205. text = obj._render_string(
  1206. sparse_columns=sparse_columns,
  1207. sparse_index=sparse_index,
  1208. max_rows=max_rows,
  1209. max_cols=max_columns,
  1210. delimiter=delimiter,
  1211. )
  1212. return save_to_buffer(
  1213. text, buf=buf, encoding=(encoding if buf is not None else None)
  1214. )
  1215. def set_td_classes(self, classes: DataFrame) -> Styler:
  1216. """
  1217. Set the ``class`` attribute of ``<td>`` HTML elements.
  1218. Parameters
  1219. ----------
  1220. classes : DataFrame
  1221. DataFrame containing strings that will be translated to CSS classes,
  1222. mapped by identical column and index key values that must exist on the
  1223. underlying Styler data. None, NaN values, and empty strings will
  1224. be ignored and not affect the rendered HTML.
  1225. Returns
  1226. -------
  1227. Styler
  1228. See Also
  1229. --------
  1230. Styler.set_table_styles: Set the table styles included within the ``<style>``
  1231. HTML element.
  1232. Styler.set_table_attributes: Set the table attributes added to the ``<table>``
  1233. HTML element.
  1234. Notes
  1235. -----
  1236. Can be used in combination with ``Styler.set_table_styles`` to define an
  1237. internal CSS solution without reference to external CSS files.
  1238. Examples
  1239. --------
  1240. >>> df = pd.DataFrame(data=[[1, 2, 3], [4, 5, 6]], columns=["A", "B", "C"])
  1241. >>> classes = pd.DataFrame([
  1242. ... ["min-val red", "", "blue"],
  1243. ... ["red", None, "blue max-val"]
  1244. ... ], index=df.index, columns=df.columns)
  1245. >>> df.style.set_td_classes(classes) # doctest: +SKIP
  1246. Using `MultiIndex` columns and a `classes` `DataFrame` as a subset of the
  1247. underlying,
  1248. >>> df = pd.DataFrame([[1,2],[3,4]], index=["a", "b"],
  1249. ... columns=[["level0", "level0"], ["level1a", "level1b"]])
  1250. >>> classes = pd.DataFrame(["min-val"], index=["a"],
  1251. ... columns=[["level0"],["level1a"]])
  1252. >>> df.style.set_td_classes(classes) # doctest: +SKIP
  1253. Form of the output with new additional css classes,
  1254. >>> df = pd.DataFrame([[1]])
  1255. >>> css = pd.DataFrame([["other-class"]])
  1256. >>> s = Styler(df, uuid="_", cell_ids=False).set_td_classes(css)
  1257. >>> s.hide(axis=0).to_html() # doctest: +SKIP
  1258. '<style type="text/css"></style>'
  1259. '<table id="T__">'
  1260. ' <thead>'
  1261. ' <tr><th class="col_heading level0 col0" >0</th></tr>'
  1262. ' </thead>'
  1263. ' <tbody>'
  1264. ' <tr><td class="data row0 col0 other-class" >1</td></tr>'
  1265. ' </tbody>'
  1266. '</table>'
  1267. """
  1268. if not classes.index.is_unique or not classes.columns.is_unique:
  1269. raise KeyError(
  1270. "Classes render only if `classes` has unique index and columns."
  1271. )
  1272. classes = classes.reindex_like(self.data)
  1273. for r, row_tup in enumerate(classes.itertuples()):
  1274. for c, value in enumerate(row_tup[1:]):
  1275. if not (pd.isna(value) or value == ""):
  1276. self.cell_context[(r, c)] = str(value)
  1277. return self
  1278. def _update_ctx(self, attrs: DataFrame) -> None:
  1279. """
  1280. Update the state of the ``Styler`` for data cells.
  1281. Collects a mapping of {index_label: [('<property>', '<value>'), ..]}.
  1282. Parameters
  1283. ----------
  1284. attrs : DataFrame
  1285. should contain strings of '<property>: <value>;<prop2>: <val2>'
  1286. Whitespace shouldn't matter and the final trailing ';' shouldn't
  1287. matter.
  1288. """
  1289. if not self.index.is_unique or not self.columns.is_unique:
  1290. raise KeyError(
  1291. "`Styler.apply` and `.applymap` are not compatible "
  1292. "with non-unique index or columns."
  1293. )
  1294. for cn in attrs.columns:
  1295. j = self.columns.get_loc(cn)
  1296. ser = attrs[cn]
  1297. for rn, c in ser.items():
  1298. if not c or pd.isna(c):
  1299. continue
  1300. css_list = maybe_convert_css_to_tuples(c)
  1301. i = self.index.get_loc(rn)
  1302. self.ctx[(i, j)].extend(css_list)
  1303. def _update_ctx_header(self, attrs: DataFrame, axis: AxisInt) -> None:
  1304. """
  1305. Update the state of the ``Styler`` for header cells.
  1306. Collects a mapping of {index_label: [('<property>', '<value>'), ..]}.
  1307. Parameters
  1308. ----------
  1309. attrs : Series
  1310. Should contain strings of '<property>: <value>;<prop2>: <val2>', and an
  1311. integer index.
  1312. Whitespace shouldn't matter and the final trailing ';' shouldn't
  1313. matter.
  1314. axis : int
  1315. Identifies whether the ctx object being updated is the index or columns
  1316. """
  1317. for j in attrs.columns:
  1318. ser = attrs[j]
  1319. for i, c in ser.items():
  1320. if not c:
  1321. continue
  1322. css_list = maybe_convert_css_to_tuples(c)
  1323. if axis == 0:
  1324. self.ctx_index[(i, j)].extend(css_list)
  1325. else:
  1326. self.ctx_columns[(j, i)].extend(css_list)
  1327. def _copy(self, deepcopy: bool = False) -> Styler:
  1328. """
  1329. Copies a Styler, allowing for deepcopy or shallow copy
  1330. Copying a Styler aims to recreate a new Styler object which contains the same
  1331. data and styles as the original.
  1332. Data dependent attributes [copied and NOT exported]:
  1333. - formatting (._display_funcs)
  1334. - hidden index values or column values (.hidden_rows, .hidden_columns)
  1335. - tooltips
  1336. - cell_context (cell css classes)
  1337. - ctx (cell css styles)
  1338. - caption
  1339. - concatenated stylers
  1340. Non-data dependent attributes [copied and exported]:
  1341. - css
  1342. - hidden index state and hidden columns state (.hide_index_, .hide_columns_)
  1343. - table_attributes
  1344. - table_styles
  1345. - applied styles (_todo)
  1346. """
  1347. # GH 40675
  1348. styler = Styler(
  1349. self.data, # populates attributes 'data', 'columns', 'index' as shallow
  1350. )
  1351. shallow = [ # simple string or boolean immutables
  1352. "hide_index_",
  1353. "hide_columns_",
  1354. "hide_column_names",
  1355. "hide_index_names",
  1356. "table_attributes",
  1357. "cell_ids",
  1358. "caption",
  1359. "uuid",
  1360. "uuid_len",
  1361. "template_latex", # also copy templates if these have been customised
  1362. "template_html_style",
  1363. "template_html_table",
  1364. "template_html",
  1365. ]
  1366. deep = [ # nested lists or dicts
  1367. "css",
  1368. "concatenated",
  1369. "_display_funcs",
  1370. "_display_funcs_index",
  1371. "_display_funcs_columns",
  1372. "hidden_rows",
  1373. "hidden_columns",
  1374. "ctx",
  1375. "ctx_index",
  1376. "ctx_columns",
  1377. "cell_context",
  1378. "_todo",
  1379. "table_styles",
  1380. "tooltips",
  1381. ]
  1382. for attr in shallow:
  1383. setattr(styler, attr, getattr(self, attr))
  1384. for attr in deep:
  1385. val = getattr(self, attr)
  1386. setattr(styler, attr, copy.deepcopy(val) if deepcopy else val)
  1387. return styler
  1388. def __copy__(self) -> Styler:
  1389. return self._copy(deepcopy=False)
  1390. def __deepcopy__(self, memo) -> Styler:
  1391. return self._copy(deepcopy=True)
  1392. def clear(self) -> None:
  1393. """
  1394. Reset the ``Styler``, removing any previously applied styles.
  1395. Returns None.
  1396. """
  1397. # create default GH 40675
  1398. clean_copy = Styler(self.data, uuid=self.uuid)
  1399. clean_attrs = [a for a in clean_copy.__dict__ if not callable(a)]
  1400. self_attrs = [a for a in self.__dict__ if not callable(a)] # maybe more attrs
  1401. for attr in clean_attrs:
  1402. setattr(self, attr, getattr(clean_copy, attr))
  1403. for attr in set(self_attrs).difference(clean_attrs):
  1404. delattr(self, attr)
  1405. def _apply(
  1406. self,
  1407. func: Callable,
  1408. axis: Axis | None = 0,
  1409. subset: Subset | None = None,
  1410. **kwargs,
  1411. ) -> Styler:
  1412. subset = slice(None) if subset is None else subset
  1413. subset = non_reducing_slice(subset)
  1414. data = self.data.loc[subset]
  1415. if data.empty:
  1416. result = DataFrame()
  1417. elif axis is None:
  1418. result = func(data, **kwargs)
  1419. if not isinstance(result, DataFrame):
  1420. if not isinstance(result, np.ndarray):
  1421. raise TypeError(
  1422. f"Function {repr(func)} must return a DataFrame or ndarray "
  1423. f"when passed to `Styler.apply` with axis=None"
  1424. )
  1425. if data.shape != result.shape:
  1426. raise ValueError(
  1427. f"Function {repr(func)} returned ndarray with wrong shape.\n"
  1428. f"Result has shape: {result.shape}\n"
  1429. f"Expected shape: {data.shape}"
  1430. )
  1431. result = DataFrame(result, index=data.index, columns=data.columns)
  1432. else:
  1433. axis = self.data._get_axis_number(axis)
  1434. if axis == 0:
  1435. result = data.apply(func, axis=0, **kwargs)
  1436. else:
  1437. result = data.T.apply(func, axis=0, **kwargs).T # see GH 42005
  1438. if isinstance(result, Series):
  1439. raise ValueError(
  1440. f"Function {repr(func)} resulted in the apply method collapsing to a "
  1441. f"Series.\nUsually, this is the result of the function returning a "
  1442. f"single value, instead of list-like."
  1443. )
  1444. msg = (
  1445. f"Function {repr(func)} created invalid {{0}} labels.\nUsually, this is "
  1446. f"the result of the function returning a "
  1447. f"{'Series' if axis is not None else 'DataFrame'} which contains invalid "
  1448. f"labels, or returning an incorrectly shaped, list-like object which "
  1449. f"cannot be mapped to labels, possibly due to applying the function along "
  1450. f"the wrong axis.\n"
  1451. f"Result {{0}} has shape: {{1}}\n"
  1452. f"Expected {{0}} shape: {{2}}"
  1453. )
  1454. if not all(result.index.isin(data.index)):
  1455. raise ValueError(msg.format("index", result.index.shape, data.index.shape))
  1456. if not all(result.columns.isin(data.columns)):
  1457. raise ValueError(
  1458. msg.format("columns", result.columns.shape, data.columns.shape)
  1459. )
  1460. self._update_ctx(result)
  1461. return self
  1462. @Substitution(subset=subset_args)
  1463. def apply(
  1464. self,
  1465. func: Callable,
  1466. axis: Axis | None = 0,
  1467. subset: Subset | None = None,
  1468. **kwargs,
  1469. ) -> Styler:
  1470. """
  1471. Apply a CSS-styling function column-wise, row-wise, or table-wise.
  1472. Updates the HTML representation with the result.
  1473. Parameters
  1474. ----------
  1475. func : function
  1476. ``func`` should take a Series if ``axis`` in [0,1] and return a list-like
  1477. object of same length, or a Series, not necessarily of same length, with
  1478. valid index labels considering ``subset``.
  1479. ``func`` should take a DataFrame if ``axis`` is ``None`` and return either
  1480. an ndarray with the same shape or a DataFrame, not necessarily of the same
  1481. shape, with valid index and columns labels considering ``subset``.
  1482. .. versionchanged:: 1.3.0
  1483. .. versionchanged:: 1.4.0
  1484. axis : {0 or 'index', 1 or 'columns', None}, default 0
  1485. Apply to each column (``axis=0`` or ``'index'``), to each row
  1486. (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
  1487. with ``axis=None``.
  1488. %(subset)s
  1489. **kwargs : dict
  1490. Pass along to ``func``.
  1491. Returns
  1492. -------
  1493. Styler
  1494. See Also
  1495. --------
  1496. Styler.applymap_index: Apply a CSS-styling function to headers elementwise.
  1497. Styler.apply_index: Apply a CSS-styling function to headers level-wise.
  1498. Styler.applymap: Apply a CSS-styling function elementwise.
  1499. Notes
  1500. -----
  1501. The elements of the output of ``func`` should be CSS styles as strings, in the
  1502. format 'attribute: value; attribute2: value2; ...' or,
  1503. if nothing is to be applied to that element, an empty string or ``None``.
  1504. This is similar to ``DataFrame.apply``, except that ``axis=None``
  1505. applies the function to the entire DataFrame at once,
  1506. rather than column-wise or row-wise.
  1507. Examples
  1508. --------
  1509. >>> def highlight_max(x, color):
  1510. ... return np.where(x == np.nanmax(x.to_numpy()), f"color: {color};", None)
  1511. >>> df = pd.DataFrame(np.random.randn(5, 2), columns=["A", "B"])
  1512. >>> df.style.apply(highlight_max, color='red') # doctest: +SKIP
  1513. >>> df.style.apply(highlight_max, color='blue', axis=1) # doctest: +SKIP
  1514. >>> df.style.apply(highlight_max, color='green', axis=None) # doctest: +SKIP
  1515. Using ``subset`` to restrict application to a single column or multiple columns
  1516. >>> df.style.apply(highlight_max, color='red', subset="A")
  1517. ... # doctest: +SKIP
  1518. >>> df.style.apply(highlight_max, color='red', subset=["A", "B"])
  1519. ... # doctest: +SKIP
  1520. Using a 2d input to ``subset`` to select rows in addition to columns
  1521. >>> df.style.apply(highlight_max, color='red', subset=([0,1,2], slice(None)))
  1522. ... # doctest: +SKIP
  1523. >>> df.style.apply(highlight_max, color='red', subset=(slice(0,5,2), "A"))
  1524. ... # doctest: +SKIP
  1525. Using a function which returns a Series / DataFrame of unequal length but
  1526. containing valid index labels
  1527. >>> df = pd.DataFrame([[1, 2], [3, 4], [4, 6]], index=["A1", "A2", "Total"])
  1528. >>> total_style = pd.Series("font-weight: bold;", index=["Total"])
  1529. >>> df.style.apply(lambda s: total_style) # doctest: +SKIP
  1530. See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for
  1531. more details.
  1532. """
  1533. self._todo.append(
  1534. (lambda instance: getattr(instance, "_apply"), (func, axis, subset), kwargs)
  1535. )
  1536. return self
  1537. def _apply_index(
  1538. self,
  1539. func: Callable,
  1540. axis: Axis = 0,
  1541. level: Level | list[Level] | None = None,
  1542. method: str = "apply",
  1543. **kwargs,
  1544. ) -> Styler:
  1545. axis = self.data._get_axis_number(axis)
  1546. obj = self.index if axis == 0 else self.columns
  1547. levels_ = refactor_levels(level, obj)
  1548. data = DataFrame(obj.to_list()).loc[:, levels_]
  1549. if method == "apply":
  1550. result = data.apply(func, axis=0, **kwargs)
  1551. elif method == "applymap":
  1552. result = data.applymap(func, **kwargs)
  1553. self._update_ctx_header(result, axis)
  1554. return self
  1555. @doc(
  1556. this="apply",
  1557. wise="level-wise",
  1558. alt="applymap",
  1559. altwise="elementwise",
  1560. func="take a Series and return a string array of the same length",
  1561. input_note="the index as a Series, if an Index, or a level of a MultiIndex",
  1562. output_note="an identically sized array of CSS styles as strings",
  1563. var="s",
  1564. ret='np.where(s == "B", "background-color: yellow;", "")',
  1565. ret2='["background-color: yellow;" if "x" in v else "" for v in s]',
  1566. )
  1567. def apply_index(
  1568. self,
  1569. func: Callable,
  1570. axis: AxisInt | str = 0,
  1571. level: Level | list[Level] | None = None,
  1572. **kwargs,
  1573. ) -> Styler:
  1574. """
  1575. Apply a CSS-styling function to the index or column headers, {wise}.
  1576. Updates the HTML representation with the result.
  1577. .. versionadded:: 1.4.0
  1578. Parameters
  1579. ----------
  1580. func : function
  1581. ``func`` should {func}.
  1582. axis : {{0, 1, "index", "columns"}}
  1583. The headers over which to apply the function.
  1584. level : int, str, list, optional
  1585. If index is MultiIndex the level(s) over which to apply the function.
  1586. **kwargs : dict
  1587. Pass along to ``func``.
  1588. Returns
  1589. -------
  1590. Styler
  1591. See Also
  1592. --------
  1593. Styler.{alt}_index: Apply a CSS-styling function to headers {altwise}.
  1594. Styler.apply: Apply a CSS-styling function column-wise, row-wise, or table-wise.
  1595. Styler.applymap: Apply a CSS-styling function elementwise.
  1596. Notes
  1597. -----
  1598. Each input to ``func`` will be {input_note}. The output of ``func`` should be
  1599. {output_note}, in the format 'attribute: value; attribute2: value2; ...'
  1600. or, if nothing is to be applied to that element, an empty string or ``None``.
  1601. Examples
  1602. --------
  1603. Basic usage to conditionally highlight values in the index.
  1604. >>> df = pd.DataFrame([[1,2], [3,4]], index=["A", "B"])
  1605. >>> def color_b(s):
  1606. ... return {ret}
  1607. >>> df.style.{this}_index(color_b) # doctest: +SKIP
  1608. .. figure:: ../../_static/style/appmaphead1.png
  1609. Selectively applying to specific levels of MultiIndex columns.
  1610. >>> midx = pd.MultiIndex.from_product([['ix', 'jy'], [0, 1], ['x3', 'z4']])
  1611. >>> df = pd.DataFrame([np.arange(8)], columns=midx)
  1612. >>> def highlight_x({var}):
  1613. ... return {ret2}
  1614. >>> df.style.{this}_index(highlight_x, axis="columns", level=[0, 2])
  1615. ... # doctest: +SKIP
  1616. .. figure:: ../../_static/style/appmaphead2.png
  1617. """
  1618. self._todo.append(
  1619. (
  1620. lambda instance: getattr(instance, "_apply_index"),
  1621. (func, axis, level, "apply"),
  1622. kwargs,
  1623. )
  1624. )
  1625. return self
  1626. @doc(
  1627. apply_index,
  1628. this="applymap",
  1629. wise="elementwise",
  1630. alt="apply",
  1631. altwise="level-wise",
  1632. func="take a scalar and return a string",
  1633. input_note="an index value, if an Index, or a level value of a MultiIndex",
  1634. output_note="CSS styles as a string",
  1635. var="v",
  1636. ret='"background-color: yellow;" if v == "B" else None',
  1637. ret2='"background-color: yellow;" if "x" in v else None',
  1638. )
  1639. def applymap_index(
  1640. self,
  1641. func: Callable,
  1642. axis: AxisInt | str = 0,
  1643. level: Level | list[Level] | None = None,
  1644. **kwargs,
  1645. ) -> Styler:
  1646. self._todo.append(
  1647. (
  1648. lambda instance: getattr(instance, "_apply_index"),
  1649. (func, axis, level, "applymap"),
  1650. kwargs,
  1651. )
  1652. )
  1653. return self
  1654. def _applymap(
  1655. self, func: Callable, subset: Subset | None = None, **kwargs
  1656. ) -> Styler:
  1657. func = partial(func, **kwargs) # applymap doesn't take kwargs?
  1658. if subset is None:
  1659. subset = IndexSlice[:]
  1660. subset = non_reducing_slice(subset)
  1661. result = self.data.loc[subset].applymap(func)
  1662. self._update_ctx(result)
  1663. return self
  1664. @Substitution(subset=subset_args)
  1665. def applymap(
  1666. self, func: Callable, subset: Subset | None = None, **kwargs
  1667. ) -> Styler:
  1668. """
  1669. Apply a CSS-styling function elementwise.
  1670. Updates the HTML representation with the result.
  1671. Parameters
  1672. ----------
  1673. func : function
  1674. ``func`` should take a scalar and return a string.
  1675. %(subset)s
  1676. **kwargs : dict
  1677. Pass along to ``func``.
  1678. Returns
  1679. -------
  1680. Styler
  1681. See Also
  1682. --------
  1683. Styler.applymap_index: Apply a CSS-styling function to headers elementwise.
  1684. Styler.apply_index: Apply a CSS-styling function to headers level-wise.
  1685. Styler.apply: Apply a CSS-styling function column-wise, row-wise, or table-wise.
  1686. Notes
  1687. -----
  1688. The elements of the output of ``func`` should be CSS styles as strings, in the
  1689. format 'attribute: value; attribute2: value2; ...' or,
  1690. if nothing is to be applied to that element, an empty string or ``None``.
  1691. Examples
  1692. --------
  1693. >>> def color_negative(v, color):
  1694. ... return f"color: {color};" if v < 0 else None
  1695. >>> df = pd.DataFrame(np.random.randn(5, 2), columns=["A", "B"])
  1696. >>> df.style.applymap(color_negative, color='red') # doctest: +SKIP
  1697. Using ``subset`` to restrict application to a single column or multiple columns
  1698. >>> df.style.applymap(color_negative, color='red', subset="A")
  1699. ... # doctest: +SKIP
  1700. >>> df.style.applymap(color_negative, color='red', subset=["A", "B"])
  1701. ... # doctest: +SKIP
  1702. Using a 2d input to ``subset`` to select rows in addition to columns
  1703. >>> df.style.applymap(color_negative, color='red',
  1704. ... subset=([0,1,2], slice(None))) # doctest: +SKIP
  1705. >>> df.style.applymap(color_negative, color='red', subset=(slice(0,5,2), "A"))
  1706. ... # doctest: +SKIP
  1707. See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for
  1708. more details.
  1709. """
  1710. self._todo.append(
  1711. (lambda instance: getattr(instance, "_applymap"), (func, subset), kwargs)
  1712. )
  1713. return self
  1714. def set_table_attributes(self, attributes: str) -> Styler:
  1715. """
  1716. Set the table attributes added to the ``<table>`` HTML element.
  1717. These are items in addition to automatic (by default) ``id`` attribute.
  1718. Parameters
  1719. ----------
  1720. attributes : str
  1721. Returns
  1722. -------
  1723. Styler
  1724. See Also
  1725. --------
  1726. Styler.set_table_styles: Set the table styles included within the ``<style>``
  1727. HTML element.
  1728. Styler.set_td_classes: Set the DataFrame of strings added to the ``class``
  1729. attribute of ``<td>`` HTML elements.
  1730. Examples
  1731. --------
  1732. >>> df = pd.DataFrame(np.random.randn(10, 4))
  1733. >>> df.style.set_table_attributes('class="pure-table"') # doctest: +SKIP
  1734. # ... <table class="pure-table"> ...
  1735. """
  1736. self.table_attributes = attributes
  1737. return self
  1738. def export(self) -> dict[str, Any]:
  1739. """
  1740. Export the styles applied to the current Styler.
  1741. Can be applied to a second Styler with ``Styler.use``.
  1742. Returns
  1743. -------
  1744. dict
  1745. See Also
  1746. --------
  1747. Styler.use: Set the styles on the current Styler.
  1748. Styler.copy: Create a copy of the current Styler.
  1749. Notes
  1750. -----
  1751. This method is designed to copy non-data dependent attributes of
  1752. one Styler to another. It differs from ``Styler.copy`` where data and
  1753. data dependent attributes are also copied.
  1754. The following items are exported since they are not generally data dependent:
  1755. - Styling functions added by the ``apply`` and ``applymap``
  1756. - Whether axes and names are hidden from the display, if unambiguous.
  1757. - Table attributes
  1758. - Table styles
  1759. The following attributes are considered data dependent and therefore not
  1760. exported:
  1761. - Caption
  1762. - UUID
  1763. - Tooltips
  1764. - Any hidden rows or columns identified by Index labels
  1765. - Any formatting applied using ``Styler.format``
  1766. - Any CSS classes added using ``Styler.set_td_classes``
  1767. Examples
  1768. --------
  1769. >>> styler = DataFrame([[1, 2], [3, 4]]).style
  1770. >>> styler2 = DataFrame([[9, 9, 9]]).style
  1771. >>> styler.hide(axis=0).highlight_max(axis=1) # doctest: +SKIP
  1772. >>> export = styler.export()
  1773. >>> styler2.use(export) # doctest: +SKIP
  1774. """
  1775. return {
  1776. "apply": copy.copy(self._todo),
  1777. "table_attributes": self.table_attributes,
  1778. "table_styles": copy.copy(self.table_styles),
  1779. "hide_index": all(self.hide_index_),
  1780. "hide_columns": all(self.hide_columns_),
  1781. "hide_index_names": self.hide_index_names,
  1782. "hide_column_names": self.hide_column_names,
  1783. "css": copy.copy(self.css),
  1784. }
  1785. def use(self, styles: dict[str, Any]) -> Styler:
  1786. """
  1787. Set the styles on the current Styler.
  1788. Possibly uses styles from ``Styler.export``.
  1789. Parameters
  1790. ----------
  1791. styles : dict(str, Any)
  1792. List of attributes to add to Styler. Dict keys should contain only:
  1793. - "apply": list of styler functions, typically added with ``apply`` or
  1794. ``applymap``.
  1795. - "table_attributes": HTML attributes, typically added with
  1796. ``set_table_attributes``.
  1797. - "table_styles": CSS selectors and properties, typically added with
  1798. ``set_table_styles``.
  1799. - "hide_index": whether the index is hidden, typically added with
  1800. ``hide_index``, or a boolean list for hidden levels.
  1801. - "hide_columns": whether column headers are hidden, typically added with
  1802. ``hide_columns``, or a boolean list for hidden levels.
  1803. - "hide_index_names": whether index names are hidden.
  1804. - "hide_column_names": whether column header names are hidden.
  1805. - "css": the css class names used.
  1806. Returns
  1807. -------
  1808. Styler
  1809. See Also
  1810. --------
  1811. Styler.export : Export the non data dependent attributes to the current Styler.
  1812. Examples
  1813. --------
  1814. >>> styler = DataFrame([[1, 2], [3, 4]]).style
  1815. >>> styler2 = DataFrame([[9, 9, 9]]).style
  1816. >>> styler.hide(axis=0).highlight_max(axis=1) # doctest: +SKIP
  1817. >>> export = styler.export()
  1818. >>> styler2.use(export) # doctest: +SKIP
  1819. """
  1820. self._todo.extend(styles.get("apply", []))
  1821. table_attributes: str = self.table_attributes or ""
  1822. obj_table_atts: str = (
  1823. ""
  1824. if styles.get("table_attributes") is None
  1825. else str(styles.get("table_attributes"))
  1826. )
  1827. self.set_table_attributes((table_attributes + " " + obj_table_atts).strip())
  1828. if styles.get("table_styles"):
  1829. self.set_table_styles(styles.get("table_styles"), overwrite=False)
  1830. for obj in ["index", "columns"]:
  1831. hide_obj = styles.get("hide_" + obj)
  1832. if hide_obj is not None:
  1833. if isinstance(hide_obj, bool):
  1834. n = getattr(self, obj).nlevels
  1835. setattr(self, "hide_" + obj + "_", [hide_obj] * n)
  1836. else:
  1837. setattr(self, "hide_" + obj + "_", hide_obj)
  1838. self.hide_index_names = styles.get("hide_index_names", False)
  1839. self.hide_column_names = styles.get("hide_column_names", False)
  1840. if styles.get("css"):
  1841. self.css = styles.get("css") # type: ignore[assignment]
  1842. return self
  1843. def set_uuid(self, uuid: str) -> Styler:
  1844. """
  1845. Set the uuid applied to ``id`` attributes of HTML elements.
  1846. Parameters
  1847. ----------
  1848. uuid : str
  1849. Returns
  1850. -------
  1851. Styler
  1852. Notes
  1853. -----
  1854. Almost all HTML elements within the table, and including the ``<table>`` element
  1855. are assigned ``id`` attributes. The format is ``T_uuid_<extra>`` where
  1856. ``<extra>`` is typically a more specific identifier, such as ``row1_col2``.
  1857. """
  1858. self.uuid = uuid
  1859. return self
  1860. def set_caption(self, caption: str | tuple | list) -> Styler:
  1861. """
  1862. Set the text added to a ``<caption>`` HTML element.
  1863. Parameters
  1864. ----------
  1865. caption : str, tuple, list
  1866. For HTML output either the string input is used or the first element of the
  1867. tuple. For LaTeX the string input provides a caption and the additional
  1868. tuple input allows for full captions and short captions, in that order.
  1869. Returns
  1870. -------
  1871. Styler
  1872. """
  1873. msg = "`caption` must be either a string or 2-tuple of strings."
  1874. if isinstance(caption, (list, tuple)):
  1875. if (
  1876. len(caption) != 2
  1877. or not isinstance(caption[0], str)
  1878. or not isinstance(caption[1], str)
  1879. ):
  1880. raise ValueError(msg)
  1881. elif not isinstance(caption, str):
  1882. raise ValueError(msg)
  1883. self.caption = caption
  1884. return self
  1885. def set_sticky(
  1886. self,
  1887. axis: Axis = 0,
  1888. pixel_size: int | None = None,
  1889. levels: Level | list[Level] | None = None,
  1890. ) -> Styler:
  1891. """
  1892. Add CSS to permanently display the index or column headers in a scrolling frame.
  1893. Parameters
  1894. ----------
  1895. axis : {0 or 'index', 1 or 'columns'}, default 0
  1896. Whether to make the index or column headers sticky.
  1897. pixel_size : int, optional
  1898. Required to configure the width of index cells or the height of column
  1899. header cells when sticking a MultiIndex (or with a named Index).
  1900. Defaults to 75 and 25 respectively.
  1901. levels : int, str, list, optional
  1902. If ``axis`` is a MultiIndex the specific levels to stick. If ``None`` will
  1903. stick all levels.
  1904. Returns
  1905. -------
  1906. Styler
  1907. Notes
  1908. -----
  1909. This method uses the CSS 'position: sticky;' property to display. It is
  1910. designed to work with visible axes, therefore both:
  1911. - `styler.set_sticky(axis="index").hide(axis="index")`
  1912. - `styler.set_sticky(axis="columns").hide(axis="columns")`
  1913. may produce strange behaviour due to CSS controls with missing elements.
  1914. """
  1915. axis = self.data._get_axis_number(axis)
  1916. obj = self.data.index if axis == 0 else self.data.columns
  1917. pixel_size = (75 if axis == 0 else 25) if not pixel_size else pixel_size
  1918. props = "position:sticky; background-color:inherit;"
  1919. if not isinstance(obj, pd.MultiIndex):
  1920. # handling MultiIndexes requires different CSS
  1921. if axis == 1:
  1922. # stick the first <tr> of <head> and, if index names, the second <tr>
  1923. # if self._hide_columns then no <thead><tr> here will exist: no conflict
  1924. styles: CSSStyles = [
  1925. {
  1926. "selector": "thead tr:nth-child(1) th",
  1927. "props": props + "top:0px; z-index:2;",
  1928. }
  1929. ]
  1930. if self.index.names[0] is not None:
  1931. styles[0]["props"] = (
  1932. props + f"top:0px; z-index:2; height:{pixel_size}px;"
  1933. )
  1934. styles.append(
  1935. {
  1936. "selector": "thead tr:nth-child(2) th",
  1937. "props": props
  1938. + f"top:{pixel_size}px; z-index:2; height:{pixel_size}px; ",
  1939. }
  1940. )
  1941. else:
  1942. # stick the first <th> of each <tr> in both <thead> and <tbody>
  1943. # if self._hide_index then no <th> will exist in <tbody>: no conflict
  1944. # but <th> will exist in <thead>: conflict with initial element
  1945. styles = [
  1946. {
  1947. "selector": "thead tr th:nth-child(1)",
  1948. "props": props + "left:0px; z-index:3 !important;",
  1949. },
  1950. {
  1951. "selector": "tbody tr th:nth-child(1)",
  1952. "props": props + "left:0px; z-index:1;",
  1953. },
  1954. ]
  1955. else:
  1956. # handle the MultiIndex case
  1957. range_idx = list(range(obj.nlevels))
  1958. levels_: list[int] = refactor_levels(levels, obj) if levels else range_idx
  1959. levels_ = sorted(levels_)
  1960. if axis == 1:
  1961. styles = []
  1962. for i, level in enumerate(levels_):
  1963. styles.append(
  1964. {
  1965. "selector": f"thead tr:nth-child({level+1}) th",
  1966. "props": props
  1967. + (
  1968. f"top:{i * pixel_size}px; height:{pixel_size}px; "
  1969. "z-index:2;"
  1970. ),
  1971. }
  1972. )
  1973. if not all(name is None for name in self.index.names):
  1974. styles.append(
  1975. {
  1976. "selector": f"thead tr:nth-child({obj.nlevels+1}) th",
  1977. "props": props
  1978. + (
  1979. f"top:{(len(levels_)) * pixel_size}px; "
  1980. f"height:{pixel_size}px; z-index:2;"
  1981. ),
  1982. }
  1983. )
  1984. else:
  1985. styles = []
  1986. for i, level in enumerate(levels_):
  1987. props_ = props + (
  1988. f"left:{i * pixel_size}px; "
  1989. f"min-width:{pixel_size}px; "
  1990. f"max-width:{pixel_size}px; "
  1991. )
  1992. styles.extend(
  1993. [
  1994. {
  1995. "selector": f"thead tr th:nth-child({level+1})",
  1996. "props": props_ + "z-index:3 !important;",
  1997. },
  1998. {
  1999. "selector": f"tbody tr th.level{level}",
  2000. "props": props_ + "z-index:1;",
  2001. },
  2002. ]
  2003. )
  2004. return self.set_table_styles(styles, overwrite=False)
  2005. def set_table_styles(
  2006. self,
  2007. table_styles: dict[Any, CSSStyles] | CSSStyles | None = None,
  2008. axis: AxisInt = 0,
  2009. overwrite: bool = True,
  2010. css_class_names: dict[str, str] | None = None,
  2011. ) -> Styler:
  2012. """
  2013. Set the table styles included within the ``<style>`` HTML element.
  2014. This function can be used to style the entire table, columns, rows or
  2015. specific HTML selectors.
  2016. Parameters
  2017. ----------
  2018. table_styles : list or dict
  2019. If supplying a list, each individual table_style should be a
  2020. dictionary with ``selector`` and ``props`` keys. ``selector``
  2021. should be a CSS selector that the style will be applied to
  2022. (automatically prefixed by the table's UUID) and ``props``
  2023. should be a list of tuples with ``(attribute, value)``.
  2024. If supplying a dict, the dict keys should correspond to
  2025. column names or index values, depending upon the specified
  2026. `axis` argument. These will be mapped to row or col CSS
  2027. selectors. MultiIndex values as dict keys should be
  2028. in their respective tuple form. The dict values should be
  2029. a list as specified in the form with CSS selectors and
  2030. props that will be applied to the specified row or column.
  2031. .. versionchanged:: 1.2.0
  2032. axis : {0 or 'index', 1 or 'columns', None}, default 0
  2033. Apply to each column (``axis=0`` or ``'index'``), to each row
  2034. (``axis=1`` or ``'columns'``). Only used if `table_styles` is
  2035. dict.
  2036. .. versionadded:: 1.2.0
  2037. overwrite : bool, default True
  2038. Styles are replaced if `True`, or extended if `False`. CSS
  2039. rules are preserved so most recent styles set will dominate
  2040. if selectors intersect.
  2041. .. versionadded:: 1.2.0
  2042. css_class_names : dict, optional
  2043. A dict of strings used to replace the default CSS classes described below.
  2044. .. versionadded:: 1.4.0
  2045. Returns
  2046. -------
  2047. Styler
  2048. See Also
  2049. --------
  2050. Styler.set_td_classes: Set the DataFrame of strings added to the ``class``
  2051. attribute of ``<td>`` HTML elements.
  2052. Styler.set_table_attributes: Set the table attributes added to the ``<table>``
  2053. HTML element.
  2054. Notes
  2055. -----
  2056. The default CSS classes dict, whose values can be replaced is as follows:
  2057. .. code-block:: python
  2058. css_class_names = {"row_heading": "row_heading",
  2059. "col_heading": "col_heading",
  2060. "index_name": "index_name",
  2061. "col": "col",
  2062. "row": "row",
  2063. "col_trim": "col_trim",
  2064. "row_trim": "row_trim",
  2065. "level": "level",
  2066. "data": "data",
  2067. "blank": "blank",
  2068. "foot": "foot"}
  2069. Examples
  2070. --------
  2071. >>> df = pd.DataFrame(np.random.randn(10, 4),
  2072. ... columns=['A', 'B', 'C', 'D'])
  2073. >>> df.style.set_table_styles(
  2074. ... [{'selector': 'tr:hover',
  2075. ... 'props': [('background-color', 'yellow')]}]
  2076. ... ) # doctest: +SKIP
  2077. Or with CSS strings
  2078. >>> df.style.set_table_styles(
  2079. ... [{'selector': 'tr:hover',
  2080. ... 'props': 'background-color: yellow; font-size: 1em;'}]
  2081. ... ) # doctest: +SKIP
  2082. Adding column styling by name
  2083. >>> df.style.set_table_styles({
  2084. ... 'A': [{'selector': '',
  2085. ... 'props': [('color', 'red')]}],
  2086. ... 'B': [{'selector': 'td',
  2087. ... 'props': 'color: blue;'}]
  2088. ... }, overwrite=False) # doctest: +SKIP
  2089. Adding row styling
  2090. >>> df.style.set_table_styles({
  2091. ... 0: [{'selector': 'td:hover',
  2092. ... 'props': [('font-size', '25px')]}]
  2093. ... }, axis=1, overwrite=False) # doctest: +SKIP
  2094. See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for
  2095. more details.
  2096. """
  2097. if css_class_names is not None:
  2098. self.css = {**self.css, **css_class_names}
  2099. if table_styles is None:
  2100. return self
  2101. elif isinstance(table_styles, dict):
  2102. axis = self.data._get_axis_number(axis)
  2103. obj = self.data.index if axis == 1 else self.data.columns
  2104. idf = f".{self.css['row']}" if axis == 1 else f".{self.css['col']}"
  2105. table_styles = [
  2106. {
  2107. "selector": str(s["selector"]) + idf + str(idx),
  2108. "props": maybe_convert_css_to_tuples(s["props"]),
  2109. }
  2110. for key, styles in table_styles.items()
  2111. for idx in obj.get_indexer_for([key])
  2112. for s in format_table_styles(styles)
  2113. ]
  2114. else:
  2115. table_styles = [
  2116. {
  2117. "selector": s["selector"],
  2118. "props": maybe_convert_css_to_tuples(s["props"]),
  2119. }
  2120. for s in table_styles
  2121. ]
  2122. if not overwrite and self.table_styles is not None:
  2123. self.table_styles.extend(table_styles)
  2124. else:
  2125. self.table_styles = table_styles
  2126. return self
  2127. def hide(
  2128. self,
  2129. subset: Subset | None = None,
  2130. axis: Axis = 0,
  2131. level: Level | list[Level] | None = None,
  2132. names: bool = False,
  2133. ) -> Styler:
  2134. """
  2135. Hide the entire index / column headers, or specific rows / columns from display.
  2136. .. versionadded:: 1.4.0
  2137. Parameters
  2138. ----------
  2139. subset : label, array-like, IndexSlice, optional
  2140. A valid 1d input or single key along the axis within
  2141. `DataFrame.loc[<subset>, :]` or `DataFrame.loc[:, <subset>]` depending
  2142. upon ``axis``, to limit ``data`` to select hidden rows / columns.
  2143. axis : {"index", 0, "columns", 1}
  2144. Apply to the index or columns.
  2145. level : int, str, list
  2146. The level(s) to hide in a MultiIndex if hiding the entire index / column
  2147. headers. Cannot be used simultaneously with ``subset``.
  2148. names : bool
  2149. Whether to hide the level name(s) of the index / columns headers in the case
  2150. it (or at least one the levels) remains visible.
  2151. Returns
  2152. -------
  2153. Styler
  2154. Notes
  2155. -----
  2156. .. warning::
  2157. This method only works with the output methods ``to_html``, ``to_string``
  2158. and ``to_latex``.
  2159. Other output methods, including ``to_excel``, ignore this hiding method
  2160. and will display all data.
  2161. This method has multiple functionality depending upon the combination
  2162. of the ``subset``, ``level`` and ``names`` arguments (see examples). The
  2163. ``axis`` argument is used only to control whether the method is applied to row
  2164. or column headers:
  2165. .. list-table:: Argument combinations
  2166. :widths: 10 20 10 60
  2167. :header-rows: 1
  2168. * - ``subset``
  2169. - ``level``
  2170. - ``names``
  2171. - Effect
  2172. * - None
  2173. - None
  2174. - False
  2175. - The axis-Index is hidden entirely.
  2176. * - None
  2177. - None
  2178. - True
  2179. - Only the axis-Index names are hidden.
  2180. * - None
  2181. - Int, Str, List
  2182. - False
  2183. - Specified axis-MultiIndex levels are hidden entirely.
  2184. * - None
  2185. - Int, Str, List
  2186. - True
  2187. - Specified axis-MultiIndex levels are hidden entirely and the names of
  2188. remaining axis-MultiIndex levels.
  2189. * - Subset
  2190. - None
  2191. - False
  2192. - The specified data rows/columns are hidden, but the axis-Index itself,
  2193. and names, remain unchanged.
  2194. * - Subset
  2195. - None
  2196. - True
  2197. - The specified data rows/columns and axis-Index names are hidden, but
  2198. the axis-Index itself remains unchanged.
  2199. * - Subset
  2200. - Int, Str, List
  2201. - Boolean
  2202. - ValueError: cannot supply ``subset`` and ``level`` simultaneously.
  2203. Note this method only hides the identifed elements so can be chained to hide
  2204. multiple elements in sequence.
  2205. Examples
  2206. --------
  2207. Simple application hiding specific rows:
  2208. >>> df = pd.DataFrame([[1,2], [3,4], [5,6]], index=["a", "b", "c"])
  2209. >>> df.style.hide(["a", "b"]) # doctest: +SKIP
  2210. 0 1
  2211. c 5 6
  2212. Hide the index and retain the data values:
  2213. >>> midx = pd.MultiIndex.from_product([["x", "y"], ["a", "b", "c"]])
  2214. >>> df = pd.DataFrame(np.random.randn(6,6), index=midx, columns=midx)
  2215. >>> df.style.format("{:.1f}").hide() # doctest: +SKIP
  2216. x y
  2217. a b c a b c
  2218. 0.1 0.0 0.4 1.3 0.6 -1.4
  2219. 0.7 1.0 1.3 1.5 -0.0 -0.2
  2220. 1.4 -0.8 1.6 -0.2 -0.4 -0.3
  2221. 0.4 1.0 -0.2 -0.8 -1.2 1.1
  2222. -0.6 1.2 1.8 1.9 0.3 0.3
  2223. 0.8 0.5 -0.3 1.2 2.2 -0.8
  2224. Hide specific rows in a MultiIndex but retain the index:
  2225. >>> df.style.format("{:.1f}").hide(subset=(slice(None), ["a", "c"]))
  2226. ... # doctest: +SKIP
  2227. x y
  2228. a b c a b c
  2229. x b 0.7 1.0 1.3 1.5 -0.0 -0.2
  2230. y b -0.6 1.2 1.8 1.9 0.3 0.3
  2231. Hide specific rows and the index through chaining:
  2232. >>> df.style.format("{:.1f}").hide(subset=(slice(None), ["a", "c"])).hide()
  2233. ... # doctest: +SKIP
  2234. x y
  2235. a b c a b c
  2236. 0.7 1.0 1.3 1.5 -0.0 -0.2
  2237. -0.6 1.2 1.8 1.9 0.3 0.3
  2238. Hide a specific level:
  2239. >>> df.style.format("{:,.1f}").hide(level=1) # doctest: +SKIP
  2240. x y
  2241. a b c a b c
  2242. x 0.1 0.0 0.4 1.3 0.6 -1.4
  2243. 0.7 1.0 1.3 1.5 -0.0 -0.2
  2244. 1.4 -0.8 1.6 -0.2 -0.4 -0.3
  2245. y 0.4 1.0 -0.2 -0.8 -1.2 1.1
  2246. -0.6 1.2 1.8 1.9 0.3 0.3
  2247. 0.8 0.5 -0.3 1.2 2.2 -0.8
  2248. Hiding just the index level names:
  2249. >>> df.index.names = ["lev0", "lev1"]
  2250. >>> df.style.format("{:,.1f}").hide(names=True) # doctest: +SKIP
  2251. x y
  2252. a b c a b c
  2253. x a 0.1 0.0 0.4 1.3 0.6 -1.4
  2254. b 0.7 1.0 1.3 1.5 -0.0 -0.2
  2255. c 1.4 -0.8 1.6 -0.2 -0.4 -0.3
  2256. y a 0.4 1.0 -0.2 -0.8 -1.2 1.1
  2257. b -0.6 1.2 1.8 1.9 0.3 0.3
  2258. c 0.8 0.5 -0.3 1.2 2.2 -0.8
  2259. Examples all produce equivalently transposed effects with ``axis="columns"``.
  2260. """
  2261. axis = self.data._get_axis_number(axis)
  2262. if axis == 0:
  2263. obj, objs, alt = "index", "index", "rows"
  2264. else:
  2265. obj, objs, alt = "column", "columns", "columns"
  2266. if level is not None and subset is not None:
  2267. raise ValueError("`subset` and `level` cannot be passed simultaneously")
  2268. if subset is None:
  2269. if level is None and names:
  2270. # this combination implies user shows the index and hides just names
  2271. setattr(self, f"hide_{obj}_names", True)
  2272. return self
  2273. levels_ = refactor_levels(level, getattr(self, objs))
  2274. setattr(
  2275. self,
  2276. f"hide_{objs}_",
  2277. [lev in levels_ for lev in range(getattr(self, objs).nlevels)],
  2278. )
  2279. else:
  2280. if axis == 0:
  2281. subset_ = IndexSlice[subset, :] # new var so mypy reads not Optional
  2282. else:
  2283. subset_ = IndexSlice[:, subset] # new var so mypy reads not Optional
  2284. subset = non_reducing_slice(subset_)
  2285. hide = self.data.loc[subset]
  2286. h_els = getattr(self, objs).get_indexer_for(getattr(hide, objs))
  2287. setattr(self, f"hidden_{alt}", h_els)
  2288. if names:
  2289. setattr(self, f"hide_{obj}_names", True)
  2290. return self
  2291. # -----------------------------------------------------------------------
  2292. # A collection of "builtin" styles
  2293. # -----------------------------------------------------------------------
  2294. def _get_numeric_subset_default(self):
  2295. # Returns a boolean mask indicating where `self.data` has numerical columns.
  2296. # Choosing a mask as opposed to the column names also works for
  2297. # boolean column labels (GH47838).
  2298. return self.data.columns.isin(self.data.select_dtypes(include=np.number))
  2299. @doc(
  2300. name="background",
  2301. alt="text",
  2302. image_prefix="bg",
  2303. text_threshold="""text_color_threshold : float or int\n
  2304. Luminance threshold for determining text color in [0, 1]. Facilitates text\n
  2305. visibility across varying background colors. All text is dark if 0, and\n
  2306. light if 1, defaults to 0.408.""",
  2307. )
  2308. @Substitution(subset=subset_args)
  2309. def background_gradient(
  2310. self,
  2311. cmap: str | Colormap = "PuBu",
  2312. low: float = 0,
  2313. high: float = 0,
  2314. axis: Axis | None = 0,
  2315. subset: Subset | None = None,
  2316. text_color_threshold: float = 0.408,
  2317. vmin: float | None = None,
  2318. vmax: float | None = None,
  2319. gmap: Sequence | None = None,
  2320. ) -> Styler:
  2321. """
  2322. Color the {name} in a gradient style.
  2323. The {name} color is determined according
  2324. to the data in each column, row or frame, or by a given
  2325. gradient map. Requires matplotlib.
  2326. Parameters
  2327. ----------
  2328. cmap : str or colormap
  2329. Matplotlib colormap.
  2330. low : float
  2331. Compress the color range at the low end. This is a multiple of the data
  2332. range to extend below the minimum; good values usually in [0, 1],
  2333. defaults to 0.
  2334. high : float
  2335. Compress the color range at the high end. This is a multiple of the data
  2336. range to extend above the maximum; good values usually in [0, 1],
  2337. defaults to 0.
  2338. axis : {{0, 1, "index", "columns", None}}, default 0
  2339. Apply to each column (``axis=0`` or ``'index'``), to each row
  2340. (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
  2341. with ``axis=None``.
  2342. %(subset)s
  2343. {text_threshold}
  2344. vmin : float, optional
  2345. Minimum data value that corresponds to colormap minimum value.
  2346. If not specified the minimum value of the data (or gmap) will be used.
  2347. vmax : float, optional
  2348. Maximum data value that corresponds to colormap maximum value.
  2349. If not specified the maximum value of the data (or gmap) will be used.
  2350. gmap : array-like, optional
  2351. Gradient map for determining the {name} colors. If not supplied
  2352. will use the underlying data from rows, columns or frame. If given as an
  2353. ndarray or list-like must be an identical shape to the underlying data
  2354. considering ``axis`` and ``subset``. If given as DataFrame or Series must
  2355. have same index and column labels considering ``axis`` and ``subset``.
  2356. If supplied, ``vmin`` and ``vmax`` should be given relative to this
  2357. gradient map.
  2358. .. versionadded:: 1.3.0
  2359. Returns
  2360. -------
  2361. Styler
  2362. See Also
  2363. --------
  2364. Styler.{alt}_gradient: Color the {alt} in a gradient style.
  2365. Notes
  2366. -----
  2367. When using ``low`` and ``high`` the range
  2368. of the gradient, given by the data if ``gmap`` is not given or by ``gmap``,
  2369. is extended at the low end effectively by
  2370. `map.min - low * map.range` and at the high end by
  2371. `map.max + high * map.range` before the colors are normalized and determined.
  2372. If combining with ``vmin`` and ``vmax`` the `map.min`, `map.max` and
  2373. `map.range` are replaced by values according to the values derived from
  2374. ``vmin`` and ``vmax``.
  2375. This method will preselect numeric columns and ignore non-numeric columns
  2376. unless a ``gmap`` is supplied in which case no preselection occurs.
  2377. Examples
  2378. --------
  2379. >>> df = pd.DataFrame(columns=["City", "Temp (c)", "Rain (mm)", "Wind (m/s)"],
  2380. ... data=[["Stockholm", 21.6, 5.0, 3.2],
  2381. ... ["Oslo", 22.4, 13.3, 3.1],
  2382. ... ["Copenhagen", 24.5, 0.0, 6.7]])
  2383. Shading the values column-wise, with ``axis=0``, preselecting numeric columns
  2384. >>> df.style.{name}_gradient(axis=0) # doctest: +SKIP
  2385. .. figure:: ../../_static/style/{image_prefix}_ax0.png
  2386. Shading all values collectively using ``axis=None``
  2387. >>> df.style.{name}_gradient(axis=None) # doctest: +SKIP
  2388. .. figure:: ../../_static/style/{image_prefix}_axNone.png
  2389. Compress the color map from the both ``low`` and ``high`` ends
  2390. >>> df.style.{name}_gradient(axis=None, low=0.75, high=1.0) # doctest: +SKIP
  2391. .. figure:: ../../_static/style/{image_prefix}_axNone_lowhigh.png
  2392. Manually setting ``vmin`` and ``vmax`` gradient thresholds
  2393. >>> df.style.{name}_gradient(axis=None, vmin=6.7, vmax=21.6) # doctest: +SKIP
  2394. .. figure:: ../../_static/style/{image_prefix}_axNone_vminvmax.png
  2395. Setting a ``gmap`` and applying to all columns with another ``cmap``
  2396. >>> df.style.{name}_gradient(axis=0, gmap=df['Temp (c)'], cmap='YlOrRd')
  2397. ... # doctest: +SKIP
  2398. .. figure:: ../../_static/style/{image_prefix}_gmap.png
  2399. Setting the gradient map for a dataframe (i.e. ``axis=None``), we need to
  2400. explicitly state ``subset`` to match the ``gmap`` shape
  2401. >>> gmap = np.array([[1,2,3], [2,3,4], [3,4,5]])
  2402. >>> df.style.{name}_gradient(axis=None, gmap=gmap,
  2403. ... cmap='YlOrRd', subset=['Temp (c)', 'Rain (mm)', 'Wind (m/s)']
  2404. ... ) # doctest: +SKIP
  2405. .. figure:: ../../_static/style/{image_prefix}_axNone_gmap.png
  2406. """
  2407. if subset is None and gmap is None:
  2408. subset = self._get_numeric_subset_default()
  2409. self.apply(
  2410. _background_gradient,
  2411. cmap=cmap,
  2412. subset=subset,
  2413. axis=axis,
  2414. low=low,
  2415. high=high,
  2416. text_color_threshold=text_color_threshold,
  2417. vmin=vmin,
  2418. vmax=vmax,
  2419. gmap=gmap,
  2420. )
  2421. return self
  2422. @doc(
  2423. background_gradient,
  2424. name="text",
  2425. alt="background",
  2426. image_prefix="tg",
  2427. text_threshold="",
  2428. )
  2429. def text_gradient(
  2430. self,
  2431. cmap: str | Colormap = "PuBu",
  2432. low: float = 0,
  2433. high: float = 0,
  2434. axis: Axis | None = 0,
  2435. subset: Subset | None = None,
  2436. vmin: float | None = None,
  2437. vmax: float | None = None,
  2438. gmap: Sequence | None = None,
  2439. ) -> Styler:
  2440. if subset is None and gmap is None:
  2441. subset = self._get_numeric_subset_default()
  2442. return self.apply(
  2443. _background_gradient,
  2444. cmap=cmap,
  2445. subset=subset,
  2446. axis=axis,
  2447. low=low,
  2448. high=high,
  2449. vmin=vmin,
  2450. vmax=vmax,
  2451. gmap=gmap,
  2452. text_only=True,
  2453. )
  2454. @Substitution(subset=subset_args)
  2455. def set_properties(self, subset: Subset | None = None, **kwargs) -> Styler:
  2456. """
  2457. Set defined CSS-properties to each ``<td>`` HTML element for the given subset.
  2458. Parameters
  2459. ----------
  2460. %(subset)s
  2461. **kwargs : dict
  2462. A dictionary of property, value pairs to be set for each cell.
  2463. Returns
  2464. -------
  2465. Styler
  2466. Notes
  2467. -----
  2468. This is a convenience methods which wraps the :meth:`Styler.applymap` calling a
  2469. function returning the CSS-properties independently of the data.
  2470. Examples
  2471. --------
  2472. >>> df = pd.DataFrame(np.random.randn(10, 4))
  2473. >>> df.style.set_properties(color="white", align="right") # doctest: +SKIP
  2474. >>> df.style.set_properties(**{'background-color': 'yellow'}) # doctest: +SKIP
  2475. See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for
  2476. more details.
  2477. """
  2478. values = "".join([f"{p}: {v};" for p, v in kwargs.items()])
  2479. return self.applymap(lambda x: values, subset=subset)
  2480. @Substitution(subset=subset_args)
  2481. def bar( # pylint: disable=disallowed-name
  2482. self,
  2483. subset: Subset | None = None,
  2484. axis: Axis | None = 0,
  2485. *,
  2486. color: str | list | tuple | None = None,
  2487. cmap: Any | None = None,
  2488. width: float = 100,
  2489. height: float = 100,
  2490. align: str | float | Callable = "mid",
  2491. vmin: float | None = None,
  2492. vmax: float | None = None,
  2493. props: str = "width: 10em;",
  2494. ) -> Styler:
  2495. """
  2496. Draw bar chart in the cell backgrounds.
  2497. .. versionchanged:: 1.4.0
  2498. Parameters
  2499. ----------
  2500. %(subset)s
  2501. axis : {0 or 'index', 1 or 'columns', None}, default 0
  2502. Apply to each column (``axis=0`` or ``'index'``), to each row
  2503. (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
  2504. with ``axis=None``.
  2505. color : str or 2-tuple/list
  2506. If a str is passed, the color is the same for both
  2507. negative and positive numbers. If 2-tuple/list is used, the
  2508. first element is the color_negative and the second is the
  2509. color_positive (eg: ['#d65f5f', '#5fba7d']).
  2510. cmap : str, matplotlib.cm.ColorMap
  2511. A string name of a matplotlib Colormap, or a Colormap object. Cannot be
  2512. used together with ``color``.
  2513. .. versionadded:: 1.4.0
  2514. width : float, default 100
  2515. The percentage of the cell, measured from the left, in which to draw the
  2516. bars, in [0, 100].
  2517. height : float, default 100
  2518. The percentage height of the bar in the cell, centrally aligned, in [0,100].
  2519. .. versionadded:: 1.4.0
  2520. align : str, int, float, callable, default 'mid'
  2521. How to align the bars within the cells relative to a width adjusted center.
  2522. If string must be one of:
  2523. - 'left' : bars are drawn rightwards from the minimum data value.
  2524. - 'right' : bars are drawn leftwards from the maximum data value.
  2525. - 'zero' : a value of zero is located at the center of the cell.
  2526. - 'mid' : a value of (max-min)/2 is located at the center of the cell,
  2527. or if all values are negative (positive) the zero is
  2528. aligned at the right (left) of the cell.
  2529. - 'mean' : the mean value of the data is located at the center of the cell.
  2530. If a float or integer is given this will indicate the center of the cell.
  2531. If a callable should take a 1d or 2d array and return a scalar.
  2532. .. versionchanged:: 1.4.0
  2533. vmin : float, optional
  2534. Minimum bar value, defining the left hand limit
  2535. of the bar drawing range, lower values are clipped to `vmin`.
  2536. When None (default): the minimum value of the data will be used.
  2537. vmax : float, optional
  2538. Maximum bar value, defining the right hand limit
  2539. of the bar drawing range, higher values are clipped to `vmax`.
  2540. When None (default): the maximum value of the data will be used.
  2541. props : str, optional
  2542. The base CSS of the cell that is extended to add the bar chart. Defaults to
  2543. `"width: 10em;"`.
  2544. .. versionadded:: 1.4.0
  2545. Returns
  2546. -------
  2547. Styler
  2548. Notes
  2549. -----
  2550. This section of the user guide:
  2551. `Table Visualization <../../user_guide/style.ipynb>`_ gives
  2552. a number of examples for different settings and color coordination.
  2553. """
  2554. if color is None and cmap is None:
  2555. color = "#d65f5f"
  2556. elif color is not None and cmap is not None:
  2557. raise ValueError("`color` and `cmap` cannot both be given")
  2558. elif color is not None:
  2559. if (isinstance(color, (list, tuple)) and len(color) > 2) or not isinstance(
  2560. color, (str, list, tuple)
  2561. ):
  2562. raise ValueError(
  2563. "`color` must be string or list or tuple of 2 strings,"
  2564. "(eg: color=['#d65f5f', '#5fba7d'])"
  2565. )
  2566. if not 0 <= width <= 100:
  2567. raise ValueError(f"`width` must be a value in [0, 100], got {width}")
  2568. if not 0 <= height <= 100:
  2569. raise ValueError(f"`height` must be a value in [0, 100], got {height}")
  2570. if subset is None:
  2571. subset = self._get_numeric_subset_default()
  2572. self.apply(
  2573. _bar,
  2574. subset=subset,
  2575. axis=axis,
  2576. align=align,
  2577. colors=color,
  2578. cmap=cmap,
  2579. width=width / 100,
  2580. height=height / 100,
  2581. vmin=vmin,
  2582. vmax=vmax,
  2583. base_css=props,
  2584. )
  2585. return self
  2586. @Substitution(
  2587. subset=subset_args,
  2588. props=properties_args,
  2589. color=coloring_args.format(default="red"),
  2590. )
  2591. def highlight_null(
  2592. self,
  2593. color: str = "red",
  2594. subset: Subset | None = None,
  2595. props: str | None = None,
  2596. ) -> Styler:
  2597. """
  2598. Highlight missing values with a style.
  2599. Parameters
  2600. ----------
  2601. %(color)s
  2602. .. versionadded:: 1.5.0
  2603. %(subset)s
  2604. .. versionadded:: 1.1.0
  2605. %(props)s
  2606. .. versionadded:: 1.3.0
  2607. Returns
  2608. -------
  2609. Styler
  2610. See Also
  2611. --------
  2612. Styler.highlight_max: Highlight the maximum with a style.
  2613. Styler.highlight_min: Highlight the minimum with a style.
  2614. Styler.highlight_between: Highlight a defined range with a style.
  2615. Styler.highlight_quantile: Highlight values defined by a quantile with a style.
  2616. """
  2617. def f(data: DataFrame, props: str) -> np.ndarray:
  2618. return np.where(pd.isna(data).to_numpy(), props, "")
  2619. if props is None:
  2620. props = f"background-color: {color};"
  2621. return self.apply(f, axis=None, subset=subset, props=props)
  2622. @Substitution(
  2623. subset=subset_args,
  2624. color=coloring_args.format(default="yellow"),
  2625. props=properties_args,
  2626. )
  2627. def highlight_max(
  2628. self,
  2629. subset: Subset | None = None,
  2630. color: str = "yellow",
  2631. axis: Axis | None = 0,
  2632. props: str | None = None,
  2633. ) -> Styler:
  2634. """
  2635. Highlight the maximum with a style.
  2636. Parameters
  2637. ----------
  2638. %(subset)s
  2639. %(color)s
  2640. axis : {0 or 'index', 1 or 'columns', None}, default 0
  2641. Apply to each column (``axis=0`` or ``'index'``), to each row
  2642. (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
  2643. with ``axis=None``.
  2644. %(props)s
  2645. .. versionadded:: 1.3.0
  2646. Returns
  2647. -------
  2648. Styler
  2649. See Also
  2650. --------
  2651. Styler.highlight_null: Highlight missing values with a style.
  2652. Styler.highlight_min: Highlight the minimum with a style.
  2653. Styler.highlight_between: Highlight a defined range with a style.
  2654. Styler.highlight_quantile: Highlight values defined by a quantile with a style.
  2655. """
  2656. if props is None:
  2657. props = f"background-color: {color};"
  2658. return self.apply(
  2659. partial(_highlight_value, op="max"),
  2660. axis=axis,
  2661. subset=subset,
  2662. props=props,
  2663. )
  2664. @Substitution(
  2665. subset=subset_args,
  2666. color=coloring_args.format(default="yellow"),
  2667. props=properties_args,
  2668. )
  2669. def highlight_min(
  2670. self,
  2671. subset: Subset | None = None,
  2672. color: str = "yellow",
  2673. axis: Axis | None = 0,
  2674. props: str | None = None,
  2675. ) -> Styler:
  2676. """
  2677. Highlight the minimum with a style.
  2678. Parameters
  2679. ----------
  2680. %(subset)s
  2681. %(color)s
  2682. axis : {0 or 'index', 1 or 'columns', None}, default 0
  2683. Apply to each column (``axis=0`` or ``'index'``), to each row
  2684. (``axis=1`` or ``'columns'``), or to the entire DataFrame at once
  2685. with ``axis=None``.
  2686. %(props)s
  2687. .. versionadded:: 1.3.0
  2688. Returns
  2689. -------
  2690. Styler
  2691. See Also
  2692. --------
  2693. Styler.highlight_null: Highlight missing values with a style.
  2694. Styler.highlight_max: Highlight the maximum with a style.
  2695. Styler.highlight_between: Highlight a defined range with a style.
  2696. Styler.highlight_quantile: Highlight values defined by a quantile with a style.
  2697. """
  2698. if props is None:
  2699. props = f"background-color: {color};"
  2700. return self.apply(
  2701. partial(_highlight_value, op="min"),
  2702. axis=axis,
  2703. subset=subset,
  2704. props=props,
  2705. )
  2706. @Substitution(
  2707. subset=subset_args,
  2708. color=coloring_args.format(default="yellow"),
  2709. props=properties_args,
  2710. )
  2711. def highlight_between(
  2712. self,
  2713. subset: Subset | None = None,
  2714. color: str = "yellow",
  2715. axis: Axis | None = 0,
  2716. left: Scalar | Sequence | None = None,
  2717. right: Scalar | Sequence | None = None,
  2718. inclusive: str = "both",
  2719. props: str | None = None,
  2720. ) -> Styler:
  2721. """
  2722. Highlight a defined range with a style.
  2723. .. versionadded:: 1.3.0
  2724. Parameters
  2725. ----------
  2726. %(subset)s
  2727. %(color)s
  2728. axis : {0 or 'index', 1 or 'columns', None}, default 0
  2729. If ``left`` or ``right`` given as sequence, axis along which to apply those
  2730. boundaries. See examples.
  2731. left : scalar or datetime-like, or sequence or array-like, default None
  2732. Left bound for defining the range.
  2733. right : scalar or datetime-like, or sequence or array-like, default None
  2734. Right bound for defining the range.
  2735. inclusive : {'both', 'neither', 'left', 'right'}
  2736. Identify whether bounds are closed or open.
  2737. %(props)s
  2738. Returns
  2739. -------
  2740. Styler
  2741. See Also
  2742. --------
  2743. Styler.highlight_null: Highlight missing values with a style.
  2744. Styler.highlight_max: Highlight the maximum with a style.
  2745. Styler.highlight_min: Highlight the minimum with a style.
  2746. Styler.highlight_quantile: Highlight values defined by a quantile with a style.
  2747. Notes
  2748. -----
  2749. If ``left`` is ``None`` only the right bound is applied.
  2750. If ``right`` is ``None`` only the left bound is applied. If both are ``None``
  2751. all values are highlighted.
  2752. ``axis`` is only needed if ``left`` or ``right`` are provided as a sequence or
  2753. an array-like object for aligning the shapes. If ``left`` and ``right`` are
  2754. both scalars then all ``axis`` inputs will give the same result.
  2755. This function only works with compatible ``dtypes``. For example a datetime-like
  2756. region can only use equivalent datetime-like ``left`` and ``right`` arguments.
  2757. Use ``subset`` to control regions which have multiple ``dtypes``.
  2758. Examples
  2759. --------
  2760. Basic usage
  2761. >>> df = pd.DataFrame({
  2762. ... 'One': [1.2, 1.6, 1.5],
  2763. ... 'Two': [2.9, 2.1, 2.5],
  2764. ... 'Three': [3.1, 3.2, 3.8],
  2765. ... })
  2766. >>> df.style.highlight_between(left=2.1, right=2.9) # doctest: +SKIP
  2767. .. figure:: ../../_static/style/hbetw_basic.png
  2768. Using a range input sequence along an ``axis``, in this case setting a ``left``
  2769. and ``right`` for each column individually
  2770. >>> df.style.highlight_between(left=[1.4, 2.4, 3.4], right=[1.6, 2.6, 3.6],
  2771. ... axis=1, color="#fffd75") # doctest: +SKIP
  2772. .. figure:: ../../_static/style/hbetw_seq.png
  2773. Using ``axis=None`` and providing the ``left`` argument as an array that
  2774. matches the input DataFrame, with a constant ``right``
  2775. >>> df.style.highlight_between(left=[[2,2,3],[2,2,3],[3,3,3]], right=3.5,
  2776. ... axis=None, color="#fffd75") # doctest: +SKIP
  2777. .. figure:: ../../_static/style/hbetw_axNone.png
  2778. Using ``props`` instead of default background coloring
  2779. >>> df.style.highlight_between(left=1.5, right=3.5,
  2780. ... props='font-weight:bold;color:#e83e8c') # doctest: +SKIP
  2781. .. figure:: ../../_static/style/hbetw_props.png
  2782. """
  2783. if props is None:
  2784. props = f"background-color: {color};"
  2785. return self.apply(
  2786. _highlight_between,
  2787. axis=axis,
  2788. subset=subset,
  2789. props=props,
  2790. left=left,
  2791. right=right,
  2792. inclusive=inclusive,
  2793. )
  2794. @Substitution(
  2795. subset=subset_args,
  2796. color=coloring_args.format(default="yellow"),
  2797. props=properties_args,
  2798. )
  2799. def highlight_quantile(
  2800. self,
  2801. subset: Subset | None = None,
  2802. color: str = "yellow",
  2803. axis: Axis | None = 0,
  2804. q_left: float = 0.0,
  2805. q_right: float = 1.0,
  2806. interpolation: QuantileInterpolation = "linear",
  2807. inclusive: str = "both",
  2808. props: str | None = None,
  2809. ) -> Styler:
  2810. """
  2811. Highlight values defined by a quantile with a style.
  2812. .. versionadded:: 1.3.0
  2813. Parameters
  2814. ----------
  2815. %(subset)s
  2816. %(color)s
  2817. axis : {0 or 'index', 1 or 'columns', None}, default 0
  2818. Axis along which to determine and highlight quantiles. If ``None`` quantiles
  2819. are measured over the entire DataFrame. See examples.
  2820. q_left : float, default 0
  2821. Left bound, in [0, q_right), for the target quantile range.
  2822. q_right : float, default 1
  2823. Right bound, in (q_left, 1], for the target quantile range.
  2824. interpolation : {‘linear’, ‘lower’, ‘higher’, ‘midpoint’, ‘nearest’}
  2825. Argument passed to ``Series.quantile`` or ``DataFrame.quantile`` for
  2826. quantile estimation.
  2827. inclusive : {'both', 'neither', 'left', 'right'}
  2828. Identify whether quantile bounds are closed or open.
  2829. %(props)s
  2830. Returns
  2831. -------
  2832. Styler
  2833. See Also
  2834. --------
  2835. Styler.highlight_null: Highlight missing values with a style.
  2836. Styler.highlight_max: Highlight the maximum with a style.
  2837. Styler.highlight_min: Highlight the minimum with a style.
  2838. Styler.highlight_between: Highlight a defined range with a style.
  2839. Notes
  2840. -----
  2841. This function does not work with ``str`` dtypes.
  2842. Examples
  2843. --------
  2844. Using ``axis=None`` and apply a quantile to all collective data
  2845. >>> df = pd.DataFrame(np.arange(10).reshape(2,5) + 1)
  2846. >>> df.style.highlight_quantile(axis=None, q_left=0.8, color="#fffd75")
  2847. ... # doctest: +SKIP
  2848. .. figure:: ../../_static/style/hq_axNone.png
  2849. Or highlight quantiles row-wise or column-wise, in this case by row-wise
  2850. >>> df.style.highlight_quantile(axis=1, q_left=0.8, color="#fffd75")
  2851. ... # doctest: +SKIP
  2852. .. figure:: ../../_static/style/hq_ax1.png
  2853. Use ``props`` instead of default background coloring
  2854. >>> df.style.highlight_quantile(axis=None, q_left=0.2, q_right=0.8,
  2855. ... props='font-weight:bold;color:#e83e8c') # doctest: +SKIP
  2856. .. figure:: ../../_static/style/hq_props.png
  2857. """
  2858. subset_ = slice(None) if subset is None else subset
  2859. subset_ = non_reducing_slice(subset_)
  2860. data = self.data.loc[subset_]
  2861. # after quantile is found along axis, e.g. along rows,
  2862. # applying the calculated quantile to alternate axis, e.g. to each column
  2863. quantiles = [q_left, q_right]
  2864. if axis is None:
  2865. q = Series(data.to_numpy().ravel()).quantile(
  2866. q=quantiles, interpolation=interpolation
  2867. )
  2868. axis_apply: int | None = None
  2869. else:
  2870. axis = self.data._get_axis_number(axis)
  2871. q = data.quantile(
  2872. axis=axis, numeric_only=False, q=quantiles, interpolation=interpolation
  2873. )
  2874. axis_apply = 1 - axis
  2875. if props is None:
  2876. props = f"background-color: {color};"
  2877. return self.apply(
  2878. _highlight_between,
  2879. axis=axis_apply,
  2880. subset=subset,
  2881. props=props,
  2882. left=q.iloc[0],
  2883. right=q.iloc[1],
  2884. inclusive=inclusive,
  2885. )
  2886. @classmethod
  2887. def from_custom_template(
  2888. cls, searchpath, html_table: str | None = None, html_style: str | None = None
  2889. ):
  2890. """
  2891. Factory function for creating a subclass of ``Styler``.
  2892. Uses custom templates and Jinja environment.
  2893. .. versionchanged:: 1.3.0
  2894. Parameters
  2895. ----------
  2896. searchpath : str or list
  2897. Path or paths of directories containing the templates.
  2898. html_table : str
  2899. Name of your custom template to replace the html_table template.
  2900. .. versionadded:: 1.3.0
  2901. html_style : str
  2902. Name of your custom template to replace the html_style template.
  2903. .. versionadded:: 1.3.0
  2904. Returns
  2905. -------
  2906. MyStyler : subclass of Styler
  2907. Has the correct ``env``,``template_html``, ``template_html_table`` and
  2908. ``template_html_style`` class attributes set.
  2909. """
  2910. loader = jinja2.ChoiceLoader([jinja2.FileSystemLoader(searchpath), cls.loader])
  2911. # mypy doesn't like dynamically-defined classes
  2912. # error: Variable "cls" is not valid as a type
  2913. # error: Invalid base class "cls"
  2914. class MyStyler(cls): # type: ignore[valid-type,misc]
  2915. env = jinja2.Environment(loader=loader)
  2916. if html_table:
  2917. template_html_table = env.get_template(html_table)
  2918. if html_style:
  2919. template_html_style = env.get_template(html_style)
  2920. return MyStyler
  2921. def pipe(self, func: Callable, *args, **kwargs):
  2922. """
  2923. Apply ``func(self, *args, **kwargs)``, and return the result.
  2924. Parameters
  2925. ----------
  2926. func : function
  2927. Function to apply to the Styler. Alternatively, a
  2928. ``(callable, keyword)`` tuple where ``keyword`` is a string
  2929. indicating the keyword of ``callable`` that expects the Styler.
  2930. *args : optional
  2931. Arguments passed to `func`.
  2932. **kwargs : optional
  2933. A dictionary of keyword arguments passed into ``func``.
  2934. Returns
  2935. -------
  2936. object :
  2937. The value returned by ``func``.
  2938. See Also
  2939. --------
  2940. DataFrame.pipe : Analogous method for DataFrame.
  2941. Styler.apply : Apply a CSS-styling function column-wise, row-wise, or
  2942. table-wise.
  2943. Notes
  2944. -----
  2945. Like :meth:`DataFrame.pipe`, this method can simplify the
  2946. application of several user-defined functions to a styler. Instead
  2947. of writing:
  2948. .. code-block:: python
  2949. f(g(df.style.format(precision=3), arg1=a), arg2=b, arg3=c)
  2950. users can write:
  2951. .. code-block:: python
  2952. (df.style.format(precision=3)
  2953. .pipe(g, arg1=a)
  2954. .pipe(f, arg2=b, arg3=c))
  2955. In particular, this allows users to define functions that take a
  2956. styler object, along with other parameters, and return the styler after
  2957. making styling changes (such as calling :meth:`Styler.apply` or
  2958. :meth:`Styler.set_properties`).
  2959. Examples
  2960. --------
  2961. **Common Use**
  2962. A common usage pattern is to pre-define styling operations which
  2963. can be easily applied to a generic styler in a single ``pipe`` call.
  2964. >>> def some_highlights(styler, min_color="red", max_color="blue"):
  2965. ... styler.highlight_min(color=min_color, axis=None)
  2966. ... styler.highlight_max(color=max_color, axis=None)
  2967. ... styler.highlight_null()
  2968. ... return styler
  2969. >>> df = pd.DataFrame([[1, 2, 3, pd.NA], [pd.NA, 4, 5, 6]], dtype="Int64")
  2970. >>> df.style.pipe(some_highlights, min_color="green") # doctest: +SKIP
  2971. .. figure:: ../../_static/style/df_pipe_hl.png
  2972. Since the method returns a ``Styler`` object it can be chained with other
  2973. methods as if applying the underlying highlighters directly.
  2974. >>> (df.style.format("{:.1f}")
  2975. ... .pipe(some_highlights, min_color="green")
  2976. ... .highlight_between(left=2, right=5)) # doctest: +SKIP
  2977. .. figure:: ../../_static/style/df_pipe_hl2.png
  2978. **Advanced Use**
  2979. Sometimes it may be necessary to pre-define styling functions, but in the case
  2980. where those functions rely on the styler, data or context. Since
  2981. ``Styler.use`` and ``Styler.export`` are designed to be non-data dependent,
  2982. they cannot be used for this purpose. Additionally the ``Styler.apply``
  2983. and ``Styler.format`` type methods are not context aware, so a solution
  2984. is to use ``pipe`` to dynamically wrap this functionality.
  2985. Suppose we want to code a generic styling function that highlights the final
  2986. level of a MultiIndex. The number of levels in the Index is dynamic so we
  2987. need the ``Styler`` context to define the level.
  2988. >>> def highlight_last_level(styler):
  2989. ... return styler.apply_index(
  2990. ... lambda v: "background-color: pink; color: yellow", axis="columns",
  2991. ... level=styler.columns.nlevels-1
  2992. ... ) # doctest: +SKIP
  2993. >>> df.columns = pd.MultiIndex.from_product([["A", "B"], ["X", "Y"]])
  2994. >>> df.style.pipe(highlight_last_level) # doctest: +SKIP
  2995. .. figure:: ../../_static/style/df_pipe_applymap.png
  2996. Additionally suppose we want to highlight a column header if there is any
  2997. missing data in that column.
  2998. In this case we need the data object itself to determine the effect on the
  2999. column headers.
  3000. >>> def highlight_header_missing(styler, level):
  3001. ... def dynamic_highlight(s):
  3002. ... return np.where(
  3003. ... styler.data.isna().any(), "background-color: red;", ""
  3004. ... )
  3005. ... return styler.apply_index(dynamic_highlight, axis=1, level=level)
  3006. >>> df.style.pipe(highlight_header_missing, level=1) # doctest: +SKIP
  3007. .. figure:: ../../_static/style/df_pipe_applydata.png
  3008. """
  3009. return com.pipe(self, func, *args, **kwargs)
  3010. def _validate_apply_axis_arg(
  3011. arg: NDFrame | Sequence | np.ndarray,
  3012. arg_name: str,
  3013. dtype: Any | None,
  3014. data: NDFrame,
  3015. ) -> np.ndarray:
  3016. """
  3017. For the apply-type methods, ``axis=None`` creates ``data`` as DataFrame, and for
  3018. ``axis=[1,0]`` it creates a Series. Where ``arg`` is expected as an element
  3019. of some operator with ``data`` we must make sure that the two are compatible shapes,
  3020. or raise.
  3021. Parameters
  3022. ----------
  3023. arg : sequence, Series or DataFrame
  3024. the user input arg
  3025. arg_name : string
  3026. name of the arg for use in error messages
  3027. dtype : numpy dtype, optional
  3028. forced numpy dtype if given
  3029. data : Series or DataFrame
  3030. underling subset of Styler data on which operations are performed
  3031. Returns
  3032. -------
  3033. ndarray
  3034. """
  3035. dtype = {"dtype": dtype} if dtype else {}
  3036. # raise if input is wrong for axis:
  3037. if isinstance(arg, Series) and isinstance(data, DataFrame):
  3038. raise ValueError(
  3039. f"'{arg_name}' is a Series but underlying data for operations "
  3040. f"is a DataFrame since 'axis=None'"
  3041. )
  3042. if isinstance(arg, DataFrame) and isinstance(data, Series):
  3043. raise ValueError(
  3044. f"'{arg_name}' is a DataFrame but underlying data for "
  3045. f"operations is a Series with 'axis in [0,1]'"
  3046. )
  3047. if isinstance(arg, (Series, DataFrame)): # align indx / cols to data
  3048. arg = arg.reindex_like(data, method=None).to_numpy(**dtype)
  3049. else:
  3050. arg = np.asarray(arg, **dtype)
  3051. assert isinstance(arg, np.ndarray) # mypy requirement
  3052. if arg.shape != data.shape: # check valid input
  3053. raise ValueError(
  3054. f"supplied '{arg_name}' is not correct shape for data over "
  3055. f"selected 'axis': got {arg.shape}, "
  3056. f"expected {data.shape}"
  3057. )
  3058. return arg
  3059. def _background_gradient(
  3060. data,
  3061. cmap: str | Colormap = "PuBu",
  3062. low: float = 0,
  3063. high: float = 0,
  3064. text_color_threshold: float = 0.408,
  3065. vmin: float | None = None,
  3066. vmax: float | None = None,
  3067. gmap: Sequence | np.ndarray | DataFrame | Series | None = None,
  3068. text_only: bool = False,
  3069. ):
  3070. """
  3071. Color background in a range according to the data or a gradient map
  3072. """
  3073. if gmap is None: # the data is used the gmap
  3074. gmap = data.to_numpy(dtype=float, na_value=np.nan)
  3075. else: # else validate gmap against the underlying data
  3076. gmap = _validate_apply_axis_arg(gmap, "gmap", float, data)
  3077. with _mpl(Styler.background_gradient) as (_, _matplotlib):
  3078. smin = np.nanmin(gmap) if vmin is None else vmin
  3079. smax = np.nanmax(gmap) if vmax is None else vmax
  3080. rng = smax - smin
  3081. # extend lower / upper bounds, compresses color range
  3082. norm = _matplotlib.colors.Normalize(smin - (rng * low), smax + (rng * high))
  3083. if cmap is None:
  3084. rgbas = _matplotlib.colormaps[_matplotlib.rcParams["image.cmap"]](
  3085. norm(gmap)
  3086. )
  3087. else:
  3088. rgbas = _matplotlib.colormaps.get_cmap(cmap)(norm(gmap))
  3089. def relative_luminance(rgba) -> float:
  3090. """
  3091. Calculate relative luminance of a color.
  3092. The calculation adheres to the W3C standards
  3093. (https://www.w3.org/WAI/GL/wiki/Relative_luminance)
  3094. Parameters
  3095. ----------
  3096. color : rgb or rgba tuple
  3097. Returns
  3098. -------
  3099. float
  3100. The relative luminance as a value from 0 to 1
  3101. """
  3102. r, g, b = (
  3103. x / 12.92 if x <= 0.04045 else ((x + 0.055) / 1.055) ** 2.4
  3104. for x in rgba[:3]
  3105. )
  3106. return 0.2126 * r + 0.7152 * g + 0.0722 * b
  3107. def css(rgba, text_only) -> str:
  3108. if not text_only:
  3109. dark = relative_luminance(rgba) < text_color_threshold
  3110. text_color = "#f1f1f1" if dark else "#000000"
  3111. return (
  3112. f"background-color: {_matplotlib.colors.rgb2hex(rgba)};"
  3113. + f"color: {text_color};"
  3114. )
  3115. else:
  3116. return f"color: {_matplotlib.colors.rgb2hex(rgba)};"
  3117. if data.ndim == 1:
  3118. return [css(rgba, text_only) for rgba in rgbas]
  3119. else:
  3120. return DataFrame(
  3121. [[css(rgba, text_only) for rgba in row] for row in rgbas],
  3122. index=data.index,
  3123. columns=data.columns,
  3124. )
  3125. def _highlight_between(
  3126. data: NDFrame,
  3127. props: str,
  3128. left: Scalar | Sequence | np.ndarray | NDFrame | None = None,
  3129. right: Scalar | Sequence | np.ndarray | NDFrame | None = None,
  3130. inclusive: bool | str = True,
  3131. ) -> np.ndarray:
  3132. """
  3133. Return an array of css props based on condition of data values within given range.
  3134. """
  3135. if np.iterable(left) and not isinstance(left, str):
  3136. left = _validate_apply_axis_arg(left, "left", None, data)
  3137. if np.iterable(right) and not isinstance(right, str):
  3138. right = _validate_apply_axis_arg(right, "right", None, data)
  3139. # get ops with correct boundary attribution
  3140. if inclusive == "both":
  3141. ops = (operator.ge, operator.le)
  3142. elif inclusive == "neither":
  3143. ops = (operator.gt, operator.lt)
  3144. elif inclusive == "left":
  3145. ops = (operator.ge, operator.lt)
  3146. elif inclusive == "right":
  3147. ops = (operator.gt, operator.le)
  3148. else:
  3149. raise ValueError(
  3150. f"'inclusive' values can be 'both', 'left', 'right', or 'neither' "
  3151. f"got {inclusive}"
  3152. )
  3153. g_left = (
  3154. # error: Argument 2 to "ge" has incompatible type "Union[str, float,
  3155. # Period, Timedelta, Interval[Any], datetime64, timedelta64, datetime,
  3156. # Sequence[Any], ndarray[Any, Any], NDFrame]"; expected "Union
  3157. # [SupportsDunderLE, SupportsDunderGE, SupportsDunderGT, SupportsDunderLT]"
  3158. ops[0](data, left) # type: ignore[arg-type]
  3159. if left is not None
  3160. else np.full(data.shape, True, dtype=bool)
  3161. )
  3162. if isinstance(g_left, (DataFrame, Series)):
  3163. g_left = g_left.where(pd.notna(g_left), False)
  3164. l_right = (
  3165. # error: Argument 2 to "le" has incompatible type "Union[str, float,
  3166. # Period, Timedelta, Interval[Any], datetime64, timedelta64, datetime,
  3167. # Sequence[Any], ndarray[Any, Any], NDFrame]"; expected "Union
  3168. # [SupportsDunderLE, SupportsDunderGE, SupportsDunderGT, SupportsDunderLT]"
  3169. ops[1](data, right) # type: ignore[arg-type]
  3170. if right is not None
  3171. else np.full(data.shape, True, dtype=bool)
  3172. )
  3173. if isinstance(l_right, (DataFrame, Series)):
  3174. l_right = l_right.where(pd.notna(l_right), False)
  3175. return np.where(g_left & l_right, props, "")
  3176. def _highlight_value(data: DataFrame | Series, op: str, props: str) -> np.ndarray:
  3177. """
  3178. Return an array of css strings based on the condition of values matching an op.
  3179. """
  3180. value = getattr(data, op)(skipna=True)
  3181. if isinstance(data, DataFrame): # min/max must be done twice to return scalar
  3182. value = getattr(value, op)(skipna=True)
  3183. cond = data == value
  3184. cond = cond.where(pd.notna(cond), False)
  3185. return np.where(cond, props, "")
  3186. def _bar(
  3187. data: NDFrame,
  3188. align: str | float | Callable,
  3189. colors: str | list | tuple,
  3190. cmap: Any,
  3191. width: float,
  3192. height: float,
  3193. vmin: float | None,
  3194. vmax: float | None,
  3195. base_css: str,
  3196. ):
  3197. """
  3198. Draw bar chart in data cells using HTML CSS linear gradient.
  3199. Parameters
  3200. ----------
  3201. data : Series or DataFrame
  3202. Underling subset of Styler data on which operations are performed.
  3203. align : str in {"left", "right", "mid", "zero", "mean"}, int, float, callable
  3204. Method for how bars are structured or scalar value of centre point.
  3205. colors : list-like of str
  3206. Two listed colors as string in valid CSS.
  3207. width : float in [0,1]
  3208. The percentage of the cell, measured from left, where drawn bars will reside.
  3209. height : float in [0,1]
  3210. The percentage of the cell's height where drawn bars will reside, centrally
  3211. aligned.
  3212. vmin : float, optional
  3213. Overwrite the minimum value of the window.
  3214. vmax : float, optional
  3215. Overwrite the maximum value of the window.
  3216. base_css : str
  3217. Additional CSS that is included in the cell before bars are drawn.
  3218. """
  3219. def css_bar(start: float, end: float, color: str) -> str:
  3220. """
  3221. Generate CSS code to draw a bar from start to end in a table cell.
  3222. Uses linear-gradient.
  3223. Parameters
  3224. ----------
  3225. start : float
  3226. Relative positional start of bar coloring in [0,1]
  3227. end : float
  3228. Relative positional end of the bar coloring in [0,1]
  3229. color : str
  3230. CSS valid color to apply.
  3231. Returns
  3232. -------
  3233. str : The CSS applicable to the cell.
  3234. Notes
  3235. -----
  3236. Uses ``base_css`` from outer scope.
  3237. """
  3238. cell_css = base_css
  3239. if end > start:
  3240. cell_css += "background: linear-gradient(90deg,"
  3241. if start > 0:
  3242. cell_css += f" transparent {start*100:.1f}%, {color} {start*100:.1f}%,"
  3243. cell_css += f" {color} {end*100:.1f}%, transparent {end*100:.1f}%)"
  3244. return cell_css
  3245. def css_calc(x, left: float, right: float, align: str, color: str | list | tuple):
  3246. """
  3247. Return the correct CSS for bar placement based on calculated values.
  3248. Parameters
  3249. ----------
  3250. x : float
  3251. Value which determines the bar placement.
  3252. left : float
  3253. Value marking the left side of calculation, usually minimum of data.
  3254. right : float
  3255. Value marking the right side of the calculation, usually maximum of data
  3256. (left < right).
  3257. align : {"left", "right", "zero", "mid"}
  3258. How the bars will be positioned.
  3259. "left", "right", "zero" can be used with any values for ``left``, ``right``.
  3260. "mid" can only be used where ``left <= 0`` and ``right >= 0``.
  3261. "zero" is used to specify a center when all values ``x``, ``left``,
  3262. ``right`` are translated, e.g. by say a mean or median.
  3263. Returns
  3264. -------
  3265. str : Resultant CSS with linear gradient.
  3266. Notes
  3267. -----
  3268. Uses ``colors``, ``width`` and ``height`` from outer scope.
  3269. """
  3270. if pd.isna(x):
  3271. return base_css
  3272. if isinstance(color, (list, tuple)):
  3273. color = color[0] if x < 0 else color[1]
  3274. assert isinstance(color, str) # mypy redefinition
  3275. x = left if x < left else x
  3276. x = right if x > right else x # trim data if outside of the window
  3277. start: float = 0
  3278. end: float = 1
  3279. if align == "left":
  3280. # all proportions are measured from the left side between left and right
  3281. end = (x - left) / (right - left)
  3282. elif align == "right":
  3283. # all proportions are measured from the right side between left and right
  3284. start = (x - left) / (right - left)
  3285. else:
  3286. z_frac: float = 0.5 # location of zero based on the left-right range
  3287. if align == "zero":
  3288. # all proportions are measured from the center at zero
  3289. limit: float = max(abs(left), abs(right))
  3290. left, right = -limit, limit
  3291. elif align == "mid":
  3292. # bars drawn from zero either leftwards or rightwards with center at mid
  3293. mid: float = (left + right) / 2
  3294. z_frac = (
  3295. -mid / (right - left) + 0.5 if mid < 0 else -left / (right - left)
  3296. )
  3297. if x < 0:
  3298. start, end = (x - left) / (right - left), z_frac
  3299. else:
  3300. start, end = z_frac, (x - left) / (right - left)
  3301. ret = css_bar(start * width, end * width, color)
  3302. if height < 1 and "background: linear-gradient(" in ret:
  3303. return (
  3304. ret + f" no-repeat center; background-size: 100% {height * 100:.1f}%;"
  3305. )
  3306. else:
  3307. return ret
  3308. values = data.to_numpy()
  3309. left = np.nanmin(values) if vmin is None else vmin
  3310. right = np.nanmax(values) if vmax is None else vmax
  3311. z: float = 0 # adjustment to translate data
  3312. if align == "mid":
  3313. if left >= 0: # "mid" is documented to act as "left" if all values positive
  3314. align, left = "left", 0 if vmin is None else vmin
  3315. elif right <= 0: # "mid" is documented to act as "right" if all values negative
  3316. align, right = "right", 0 if vmax is None else vmax
  3317. elif align == "mean":
  3318. z, align = np.nanmean(values), "zero"
  3319. elif callable(align):
  3320. z, align = align(values), "zero"
  3321. elif isinstance(align, (float, int)):
  3322. z, align = float(align), "zero"
  3323. elif align not in ("left", "right", "zero"):
  3324. raise ValueError(
  3325. "`align` should be in {'left', 'right', 'mid', 'mean', 'zero'} or be a "
  3326. "value defining the center line or a callable that returns a float"
  3327. )
  3328. rgbas = None
  3329. if cmap is not None:
  3330. # use the matplotlib colormap input
  3331. with _mpl(Styler.bar) as (_, _matplotlib):
  3332. cmap = (
  3333. _matplotlib.colormaps[cmap]
  3334. if isinstance(cmap, str)
  3335. else cmap # assumed to be a Colormap instance as documented
  3336. )
  3337. norm = _matplotlib.colors.Normalize(left, right)
  3338. rgbas = cmap(norm(values))
  3339. if data.ndim == 1:
  3340. rgbas = [_matplotlib.colors.rgb2hex(rgba) for rgba in rgbas]
  3341. else:
  3342. rgbas = [
  3343. [_matplotlib.colors.rgb2hex(rgba) for rgba in row] for row in rgbas
  3344. ]
  3345. assert isinstance(align, str) # mypy: should now be in [left, right, mid, zero]
  3346. if data.ndim == 1:
  3347. return [
  3348. css_calc(
  3349. x - z, left - z, right - z, align, colors if rgbas is None else rgbas[i]
  3350. )
  3351. for i, x in enumerate(values)
  3352. ]
  3353. else:
  3354. return np.array(
  3355. [
  3356. [
  3357. css_calc(
  3358. x - z,
  3359. left - z,
  3360. right - z,
  3361. align,
  3362. colors if rgbas is None else rgbas[i][j],
  3363. )
  3364. for j, x in enumerate(row)
  3365. ]
  3366. for i, row in enumerate(values)
  3367. ]
  3368. )