test_to_latex.py 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501
  1. import codecs
  2. from datetime import datetime
  3. from textwrap import dedent
  4. import pytest
  5. import pandas as pd
  6. from pandas import (
  7. DataFrame,
  8. Series,
  9. )
  10. import pandas._testing as tm
  11. from pandas.io.formats.format import DataFrameFormatter
  12. from pandas.io.formats.latex import (
  13. RegularTableBuilder,
  14. RowBodyIterator,
  15. RowHeaderIterator,
  16. RowStringConverter,
  17. )
  18. pytest.importorskip("jinja2")
  19. def _dedent(string):
  20. """Dedent without new line in the beginning.
  21. Built-in textwrap.dedent would keep new line character in the beginning
  22. of multi-line string starting from the new line.
  23. This version drops the leading new line character.
  24. """
  25. return dedent(string).lstrip()
  26. @pytest.fixture
  27. def df_short():
  28. """Short dataframe for testing table/tabular/longtable LaTeX env."""
  29. return DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  30. class TestToLatex:
  31. def test_to_latex_to_file(self, float_frame):
  32. with tm.ensure_clean("test.tex") as path:
  33. float_frame.to_latex(path)
  34. with open(path) as f:
  35. assert float_frame.to_latex() == f.read()
  36. def test_to_latex_to_file_utf8_with_encoding(self):
  37. # test with utf-8 and encoding option (GH 7061)
  38. df = DataFrame([["au\xdfgangen"]])
  39. with tm.ensure_clean("test.tex") as path:
  40. df.to_latex(path, encoding="utf-8")
  41. with codecs.open(path, "r", encoding="utf-8") as f:
  42. assert df.to_latex() == f.read()
  43. def test_to_latex_to_file_utf8_without_encoding(self):
  44. # test with utf-8 without encoding option
  45. df = DataFrame([["au\xdfgangen"]])
  46. with tm.ensure_clean("test.tex") as path:
  47. df.to_latex(path)
  48. with codecs.open(path, "r", encoding="utf-8") as f:
  49. assert df.to_latex() == f.read()
  50. def test_to_latex_tabular_with_index(self):
  51. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  52. result = df.to_latex()
  53. expected = _dedent(
  54. r"""
  55. \begin{tabular}{lrl}
  56. \toprule
  57. & a & b \\
  58. \midrule
  59. 0 & 1 & b1 \\
  60. 1 & 2 & b2 \\
  61. \bottomrule
  62. \end{tabular}
  63. """
  64. )
  65. assert result == expected
  66. def test_to_latex_tabular_without_index(self):
  67. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  68. result = df.to_latex(index=False)
  69. expected = _dedent(
  70. r"""
  71. \begin{tabular}{rl}
  72. \toprule
  73. a & b \\
  74. \midrule
  75. 1 & b1 \\
  76. 2 & b2 \\
  77. \bottomrule
  78. \end{tabular}
  79. """
  80. )
  81. assert result == expected
  82. @pytest.mark.parametrize(
  83. "bad_column_format",
  84. [5, 1.2, ["l", "r"], ("r", "c"), {"r", "c", "l"}, {"a": "r", "b": "l"}],
  85. )
  86. def test_to_latex_bad_column_format(self, bad_column_format):
  87. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  88. msg = r"`column_format` must be str or unicode"
  89. with pytest.raises(ValueError, match=msg):
  90. df.to_latex(column_format=bad_column_format)
  91. def test_to_latex_column_format_just_works(self, float_frame):
  92. # GH Bug #9402
  93. float_frame.to_latex(column_format="lcr")
  94. def test_to_latex_column_format(self):
  95. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  96. result = df.to_latex(column_format="lcr")
  97. expected = _dedent(
  98. r"""
  99. \begin{tabular}{lcr}
  100. \toprule
  101. & a & b \\
  102. \midrule
  103. 0 & 1 & b1 \\
  104. 1 & 2 & b2 \\
  105. \bottomrule
  106. \end{tabular}
  107. """
  108. )
  109. assert result == expected
  110. def test_to_latex_float_format_object_col(self):
  111. # GH#40024
  112. ser = Series([1000.0, "test"])
  113. result = ser.to_latex(float_format="{:,.0f}".format)
  114. expected = _dedent(
  115. r"""
  116. \begin{tabular}{ll}
  117. \toprule
  118. & 0 \\
  119. \midrule
  120. 0 & 1,000 \\
  121. 1 & test \\
  122. \bottomrule
  123. \end{tabular}
  124. """
  125. )
  126. assert result == expected
  127. def test_to_latex_empty_tabular(self):
  128. df = DataFrame()
  129. result = df.to_latex()
  130. expected = _dedent(
  131. r"""
  132. \begin{tabular}{l}
  133. \toprule
  134. \midrule
  135. \bottomrule
  136. \end{tabular}
  137. """
  138. )
  139. assert result == expected
  140. def test_to_latex_series(self):
  141. s = Series(["a", "b", "c"])
  142. result = s.to_latex()
  143. expected = _dedent(
  144. r"""
  145. \begin{tabular}{ll}
  146. \toprule
  147. & 0 \\
  148. \midrule
  149. 0 & a \\
  150. 1 & b \\
  151. 2 & c \\
  152. \bottomrule
  153. \end{tabular}
  154. """
  155. )
  156. assert result == expected
  157. def test_to_latex_midrule_location(self):
  158. # GH 18326
  159. df = DataFrame({"a": [1, 2]})
  160. df.index.name = "foo"
  161. result = df.to_latex(index_names=False)
  162. expected = _dedent(
  163. r"""
  164. \begin{tabular}{lr}
  165. \toprule
  166. & a \\
  167. \midrule
  168. 0 & 1 \\
  169. 1 & 2 \\
  170. \bottomrule
  171. \end{tabular}
  172. """
  173. )
  174. assert result == expected
  175. class TestToLatexLongtable:
  176. def test_to_latex_empty_longtable(self):
  177. df = DataFrame()
  178. result = df.to_latex(longtable=True)
  179. expected = _dedent(
  180. r"""
  181. \begin{longtable}{l}
  182. \toprule
  183. \midrule
  184. \endfirsthead
  185. \toprule
  186. \midrule
  187. \endhead
  188. \midrule
  189. \multicolumn{0}{r}{Continued on next page} \\
  190. \midrule
  191. \endfoot
  192. \bottomrule
  193. \endlastfoot
  194. \end{longtable}
  195. """
  196. )
  197. assert result == expected
  198. def test_to_latex_longtable_with_index(self):
  199. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  200. result = df.to_latex(longtable=True)
  201. expected = _dedent(
  202. r"""
  203. \begin{longtable}{lrl}
  204. \toprule
  205. & a & b \\
  206. \midrule
  207. \endfirsthead
  208. \toprule
  209. & a & b \\
  210. \midrule
  211. \endhead
  212. \midrule
  213. \multicolumn{3}{r}{Continued on next page} \\
  214. \midrule
  215. \endfoot
  216. \bottomrule
  217. \endlastfoot
  218. 0 & 1 & b1 \\
  219. 1 & 2 & b2 \\
  220. \end{longtable}
  221. """
  222. )
  223. assert result == expected
  224. def test_to_latex_longtable_without_index(self):
  225. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  226. result = df.to_latex(index=False, longtable=True)
  227. expected = _dedent(
  228. r"""
  229. \begin{longtable}{rl}
  230. \toprule
  231. a & b \\
  232. \midrule
  233. \endfirsthead
  234. \toprule
  235. a & b \\
  236. \midrule
  237. \endhead
  238. \midrule
  239. \multicolumn{2}{r}{Continued on next page} \\
  240. \midrule
  241. \endfoot
  242. \bottomrule
  243. \endlastfoot
  244. 1 & b1 \\
  245. 2 & b2 \\
  246. \end{longtable}
  247. """
  248. )
  249. assert result == expected
  250. @pytest.mark.parametrize(
  251. "df, expected_number",
  252. [
  253. (DataFrame({"a": [1, 2]}), 1),
  254. (DataFrame({"a": [1, 2], "b": [3, 4]}), 2),
  255. (DataFrame({"a": [1, 2], "b": [3, 4], "c": [5, 6]}), 3),
  256. ],
  257. )
  258. def test_to_latex_longtable_continued_on_next_page(self, df, expected_number):
  259. result = df.to_latex(index=False, longtable=True)
  260. assert rf"\multicolumn{{{expected_number}}}" in result
  261. class TestToLatexHeader:
  262. def test_to_latex_no_header_with_index(self):
  263. # GH 7124
  264. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  265. result = df.to_latex(header=False)
  266. expected = _dedent(
  267. r"""
  268. \begin{tabular}{lrl}
  269. \toprule
  270. \midrule
  271. 0 & 1 & b1 \\
  272. 1 & 2 & b2 \\
  273. \bottomrule
  274. \end{tabular}
  275. """
  276. )
  277. assert result == expected
  278. def test_to_latex_no_header_without_index(self):
  279. # GH 7124
  280. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  281. result = df.to_latex(index=False, header=False)
  282. expected = _dedent(
  283. r"""
  284. \begin{tabular}{rl}
  285. \toprule
  286. \midrule
  287. 1 & b1 \\
  288. 2 & b2 \\
  289. \bottomrule
  290. \end{tabular}
  291. """
  292. )
  293. assert result == expected
  294. def test_to_latex_specified_header_with_index(self):
  295. # GH 7124
  296. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  297. result = df.to_latex(header=["AA", "BB"])
  298. expected = _dedent(
  299. r"""
  300. \begin{tabular}{lrl}
  301. \toprule
  302. & AA & BB \\
  303. \midrule
  304. 0 & 1 & b1 \\
  305. 1 & 2 & b2 \\
  306. \bottomrule
  307. \end{tabular}
  308. """
  309. )
  310. assert result == expected
  311. def test_to_latex_specified_header_without_index(self):
  312. # GH 7124
  313. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  314. result = df.to_latex(header=["AA", "BB"], index=False)
  315. expected = _dedent(
  316. r"""
  317. \begin{tabular}{rl}
  318. \toprule
  319. AA & BB \\
  320. \midrule
  321. 1 & b1 \\
  322. 2 & b2 \\
  323. \bottomrule
  324. \end{tabular}
  325. """
  326. )
  327. assert result == expected
  328. @pytest.mark.parametrize(
  329. "header, num_aliases",
  330. [
  331. (["A"], 1),
  332. (("B",), 1),
  333. (("Col1", "Col2", "Col3"), 3),
  334. (("Col1", "Col2", "Col3", "Col4"), 4),
  335. ],
  336. )
  337. def test_to_latex_number_of_items_in_header_missmatch_raises(
  338. self,
  339. header,
  340. num_aliases,
  341. ):
  342. # GH 7124
  343. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  344. msg = f"Writing 2 cols but got {num_aliases} aliases"
  345. with pytest.raises(ValueError, match=msg):
  346. df.to_latex(header=header)
  347. def test_to_latex_decimal(self):
  348. # GH 12031
  349. df = DataFrame({"a": [1.0, 2.1], "b": ["b1", "b2"]})
  350. result = df.to_latex(decimal=",")
  351. expected = _dedent(
  352. r"""
  353. \begin{tabular}{lrl}
  354. \toprule
  355. & a & b \\
  356. \midrule
  357. 0 & 1,000000 & b1 \\
  358. 1 & 2,100000 & b2 \\
  359. \bottomrule
  360. \end{tabular}
  361. """
  362. )
  363. assert result == expected
  364. class TestToLatexBold:
  365. def test_to_latex_bold_rows(self):
  366. # GH 16707
  367. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  368. result = df.to_latex(bold_rows=True)
  369. expected = _dedent(
  370. r"""
  371. \begin{tabular}{lrl}
  372. \toprule
  373. & a & b \\
  374. \midrule
  375. \textbf{0} & 1 & b1 \\
  376. \textbf{1} & 2 & b2 \\
  377. \bottomrule
  378. \end{tabular}
  379. """
  380. )
  381. assert result == expected
  382. def test_to_latex_no_bold_rows(self):
  383. # GH 16707
  384. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  385. result = df.to_latex(bold_rows=False)
  386. expected = _dedent(
  387. r"""
  388. \begin{tabular}{lrl}
  389. \toprule
  390. & a & b \\
  391. \midrule
  392. 0 & 1 & b1 \\
  393. 1 & 2 & b2 \\
  394. \bottomrule
  395. \end{tabular}
  396. """
  397. )
  398. assert result == expected
  399. class TestToLatexCaptionLabel:
  400. @pytest.fixture
  401. def caption_table(self):
  402. """Caption for table/tabular LaTeX environment."""
  403. return "a table in a \\texttt{table/tabular} environment"
  404. @pytest.fixture
  405. def short_caption(self):
  406. """Short caption for testing \\caption[short_caption]{full_caption}."""
  407. return "a table"
  408. @pytest.fixture
  409. def label_table(self):
  410. """Label for table/tabular LaTeX environment."""
  411. return "tab:table_tabular"
  412. @pytest.fixture
  413. def caption_longtable(self):
  414. """Caption for longtable LaTeX environment."""
  415. return "a table in a \\texttt{longtable} environment"
  416. @pytest.fixture
  417. def label_longtable(self):
  418. """Label for longtable LaTeX environment."""
  419. return "tab:longtable"
  420. def test_to_latex_caption_only(self, df_short, caption_table):
  421. # GH 25436
  422. result = df_short.to_latex(caption=caption_table)
  423. expected = _dedent(
  424. r"""
  425. \begin{table}
  426. \caption{a table in a \texttt{table/tabular} environment}
  427. \begin{tabular}{lrl}
  428. \toprule
  429. & a & b \\
  430. \midrule
  431. 0 & 1 & b1 \\
  432. 1 & 2 & b2 \\
  433. \bottomrule
  434. \end{tabular}
  435. \end{table}
  436. """
  437. )
  438. assert result == expected
  439. def test_to_latex_label_only(self, df_short, label_table):
  440. # GH 25436
  441. result = df_short.to_latex(label=label_table)
  442. expected = _dedent(
  443. r"""
  444. \begin{table}
  445. \label{tab:table_tabular}
  446. \begin{tabular}{lrl}
  447. \toprule
  448. & a & b \\
  449. \midrule
  450. 0 & 1 & b1 \\
  451. 1 & 2 & b2 \\
  452. \bottomrule
  453. \end{tabular}
  454. \end{table}
  455. """
  456. )
  457. assert result == expected
  458. def test_to_latex_caption_and_label(self, df_short, caption_table, label_table):
  459. # GH 25436
  460. result = df_short.to_latex(caption=caption_table, label=label_table)
  461. expected = _dedent(
  462. r"""
  463. \begin{table}
  464. \caption{a table in a \texttt{table/tabular} environment}
  465. \label{tab:table_tabular}
  466. \begin{tabular}{lrl}
  467. \toprule
  468. & a & b \\
  469. \midrule
  470. 0 & 1 & b1 \\
  471. 1 & 2 & b2 \\
  472. \bottomrule
  473. \end{tabular}
  474. \end{table}
  475. """
  476. )
  477. assert result == expected
  478. def test_to_latex_caption_and_shortcaption(
  479. self,
  480. df_short,
  481. caption_table,
  482. short_caption,
  483. ):
  484. result = df_short.to_latex(caption=(caption_table, short_caption))
  485. expected = _dedent(
  486. r"""
  487. \begin{table}
  488. \caption[a table]{a table in a \texttt{table/tabular} environment}
  489. \begin{tabular}{lrl}
  490. \toprule
  491. & a & b \\
  492. \midrule
  493. 0 & 1 & b1 \\
  494. 1 & 2 & b2 \\
  495. \bottomrule
  496. \end{tabular}
  497. \end{table}
  498. """
  499. )
  500. assert result == expected
  501. def test_to_latex_caption_and_shortcaption_list_is_ok(self, df_short):
  502. caption = ("Long-long-caption", "Short")
  503. result_tuple = df_short.to_latex(caption=caption)
  504. result_list = df_short.to_latex(caption=list(caption))
  505. assert result_tuple == result_list
  506. def test_to_latex_caption_shortcaption_and_label(
  507. self,
  508. df_short,
  509. caption_table,
  510. short_caption,
  511. label_table,
  512. ):
  513. # test when the short_caption is provided alongside caption and label
  514. result = df_short.to_latex(
  515. caption=(caption_table, short_caption),
  516. label=label_table,
  517. )
  518. expected = _dedent(
  519. r"""
  520. \begin{table}
  521. \caption[a table]{a table in a \texttt{table/tabular} environment}
  522. \label{tab:table_tabular}
  523. \begin{tabular}{lrl}
  524. \toprule
  525. & a & b \\
  526. \midrule
  527. 0 & 1 & b1 \\
  528. 1 & 2 & b2 \\
  529. \bottomrule
  530. \end{tabular}
  531. \end{table}
  532. """
  533. )
  534. assert result == expected
  535. @pytest.mark.parametrize(
  536. "bad_caption",
  537. [
  538. ("full_caption", "short_caption", "extra_string"),
  539. ("full_caption", "short_caption", 1),
  540. ("full_caption", "short_caption", None),
  541. ("full_caption",),
  542. (None,),
  543. ],
  544. )
  545. def test_to_latex_bad_caption_raises(self, bad_caption):
  546. # test that wrong number of params is raised
  547. df = DataFrame({"a": [1]})
  548. msg = "`caption` must be either a string or 2-tuple of strings"
  549. with pytest.raises(ValueError, match=msg):
  550. df.to_latex(caption=bad_caption)
  551. def test_to_latex_two_chars_caption(self, df_short):
  552. # test that two chars caption is handled correctly
  553. # it must not be unpacked into long_caption, short_caption.
  554. result = df_short.to_latex(caption="xy")
  555. expected = _dedent(
  556. r"""
  557. \begin{table}
  558. \caption{xy}
  559. \begin{tabular}{lrl}
  560. \toprule
  561. & a & b \\
  562. \midrule
  563. 0 & 1 & b1 \\
  564. 1 & 2 & b2 \\
  565. \bottomrule
  566. \end{tabular}
  567. \end{table}
  568. """
  569. )
  570. assert result == expected
  571. def test_to_latex_longtable_caption_only(self, df_short, caption_longtable):
  572. # GH 25436
  573. # test when no caption and no label is provided
  574. # is performed by test_to_latex_longtable()
  575. result = df_short.to_latex(longtable=True, caption=caption_longtable)
  576. expected = _dedent(
  577. r"""
  578. \begin{longtable}{lrl}
  579. \caption{a table in a \texttt{longtable} environment} \\
  580. \toprule
  581. & a & b \\
  582. \midrule
  583. \endfirsthead
  584. \caption[]{a table in a \texttt{longtable} environment} \\
  585. \toprule
  586. & a & b \\
  587. \midrule
  588. \endhead
  589. \midrule
  590. \multicolumn{3}{r}{Continued on next page} \\
  591. \midrule
  592. \endfoot
  593. \bottomrule
  594. \endlastfoot
  595. 0 & 1 & b1 \\
  596. 1 & 2 & b2 \\
  597. \end{longtable}
  598. """
  599. )
  600. assert result == expected
  601. def test_to_latex_longtable_label_only(self, df_short, label_longtable):
  602. # GH 25436
  603. result = df_short.to_latex(longtable=True, label=label_longtable)
  604. expected = _dedent(
  605. r"""
  606. \begin{longtable}{lrl}
  607. \label{tab:longtable} \\
  608. \toprule
  609. & a & b \\
  610. \midrule
  611. \endfirsthead
  612. \toprule
  613. & a & b \\
  614. \midrule
  615. \endhead
  616. \midrule
  617. \multicolumn{3}{r}{Continued on next page} \\
  618. \midrule
  619. \endfoot
  620. \bottomrule
  621. \endlastfoot
  622. 0 & 1 & b1 \\
  623. 1 & 2 & b2 \\
  624. \end{longtable}
  625. """
  626. )
  627. assert result == expected
  628. def test_to_latex_longtable_caption_and_label(
  629. self,
  630. df_short,
  631. caption_longtable,
  632. label_longtable,
  633. ):
  634. # GH 25436
  635. result = df_short.to_latex(
  636. longtable=True,
  637. caption=caption_longtable,
  638. label=label_longtable,
  639. )
  640. expected = _dedent(
  641. r"""
  642. \begin{longtable}{lrl}
  643. \caption{a table in a \texttt{longtable} environment} \label{tab:longtable} \\
  644. \toprule
  645. & a & b \\
  646. \midrule
  647. \endfirsthead
  648. \caption[]{a table in a \texttt{longtable} environment} \\
  649. \toprule
  650. & a & b \\
  651. \midrule
  652. \endhead
  653. \midrule
  654. \multicolumn{3}{r}{Continued on next page} \\
  655. \midrule
  656. \endfoot
  657. \bottomrule
  658. \endlastfoot
  659. 0 & 1 & b1 \\
  660. 1 & 2 & b2 \\
  661. \end{longtable}
  662. """
  663. )
  664. assert result == expected
  665. def test_to_latex_longtable_caption_shortcaption_and_label(
  666. self,
  667. df_short,
  668. caption_longtable,
  669. short_caption,
  670. label_longtable,
  671. ):
  672. # test when the caption, the short_caption and the label are provided
  673. result = df_short.to_latex(
  674. longtable=True,
  675. caption=(caption_longtable, short_caption),
  676. label=label_longtable,
  677. )
  678. expected = _dedent(
  679. r"""
  680. \begin{longtable}{lrl}
  681. \caption[a table]{a table in a \texttt{longtable} environment} \label{tab:longtable} \\
  682. \toprule
  683. & a & b \\
  684. \midrule
  685. \endfirsthead
  686. \caption[]{a table in a \texttt{longtable} environment} \\
  687. \toprule
  688. & a & b \\
  689. \midrule
  690. \endhead
  691. \midrule
  692. \multicolumn{3}{r}{Continued on next page} \\
  693. \midrule
  694. \endfoot
  695. \bottomrule
  696. \endlastfoot
  697. 0 & 1 & b1 \\
  698. 1 & 2 & b2 \\
  699. \end{longtable}
  700. """
  701. )
  702. assert result == expected
  703. class TestToLatexEscape:
  704. @pytest.fixture
  705. def df_with_symbols(self):
  706. """Dataframe with special characters for testing chars escaping."""
  707. a = "a"
  708. b = "b"
  709. yield DataFrame({"co$e^x$": {a: "a", b: "b"}, "co^l1": {a: "a", b: "b"}})
  710. def test_to_latex_escape_false(self, df_with_symbols):
  711. result = df_with_symbols.to_latex(escape=False)
  712. expected = _dedent(
  713. r"""
  714. \begin{tabular}{lll}
  715. \toprule
  716. & co$e^x$ & co^l1 \\
  717. \midrule
  718. a & a & a \\
  719. b & b & b \\
  720. \bottomrule
  721. \end{tabular}
  722. """
  723. )
  724. assert result == expected
  725. def test_to_latex_escape_default(self, df_with_symbols):
  726. # gh50871: in v2.0 escape is False by default (styler.format.escape=None)
  727. default = df_with_symbols.to_latex()
  728. specified_true = df_with_symbols.to_latex(escape=True)
  729. assert default != specified_true
  730. def test_to_latex_special_escape(self):
  731. df = DataFrame([r"a\b\c", r"^a^b^c", r"~a~b~c"])
  732. result = df.to_latex(escape=True)
  733. expected = _dedent(
  734. r"""
  735. \begin{tabular}{ll}
  736. \toprule
  737. & 0 \\
  738. \midrule
  739. 0 & a\textbackslash b\textbackslash c \\
  740. 1 & \textasciicircum a\textasciicircum b\textasciicircum c \\
  741. 2 & \textasciitilde a\textasciitilde b\textasciitilde c \\
  742. \bottomrule
  743. \end{tabular}
  744. """
  745. )
  746. assert result == expected
  747. def test_to_latex_escape_special_chars(self):
  748. special_characters = ["&", "%", "$", "#", "_", "{", "}", "~", "^", "\\"]
  749. df = DataFrame(data=special_characters)
  750. result = df.to_latex(escape=True)
  751. expected = _dedent(
  752. r"""
  753. \begin{tabular}{ll}
  754. \toprule
  755. & 0 \\
  756. \midrule
  757. 0 & \& \\
  758. 1 & \% \\
  759. 2 & \$ \\
  760. 3 & \# \\
  761. 4 & \_ \\
  762. 5 & \{ \\
  763. 6 & \} \\
  764. 7 & \textasciitilde \\
  765. 8 & \textasciicircum \\
  766. 9 & \textbackslash \\
  767. \bottomrule
  768. \end{tabular}
  769. """
  770. )
  771. assert result == expected
  772. def test_to_latex_specified_header_special_chars_without_escape(self):
  773. # GH 7124
  774. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  775. result = df.to_latex(header=["$A$", "$B$"], escape=False)
  776. expected = _dedent(
  777. r"""
  778. \begin{tabular}{lrl}
  779. \toprule
  780. & $A$ & $B$ \\
  781. \midrule
  782. 0 & 1 & b1 \\
  783. 1 & 2 & b2 \\
  784. \bottomrule
  785. \end{tabular}
  786. """
  787. )
  788. assert result == expected
  789. class TestToLatexPosition:
  790. def test_to_latex_position(self):
  791. the_position = "h"
  792. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  793. result = df.to_latex(position=the_position)
  794. expected = _dedent(
  795. r"""
  796. \begin{table}[h]
  797. \begin{tabular}{lrl}
  798. \toprule
  799. & a & b \\
  800. \midrule
  801. 0 & 1 & b1 \\
  802. 1 & 2 & b2 \\
  803. \bottomrule
  804. \end{tabular}
  805. \end{table}
  806. """
  807. )
  808. assert result == expected
  809. def test_to_latex_longtable_position(self):
  810. the_position = "t"
  811. df = DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  812. result = df.to_latex(longtable=True, position=the_position)
  813. expected = _dedent(
  814. r"""
  815. \begin{longtable}[t]{lrl}
  816. \toprule
  817. & a & b \\
  818. \midrule
  819. \endfirsthead
  820. \toprule
  821. & a & b \\
  822. \midrule
  823. \endhead
  824. \midrule
  825. \multicolumn{3}{r}{Continued on next page} \\
  826. \midrule
  827. \endfoot
  828. \bottomrule
  829. \endlastfoot
  830. 0 & 1 & b1 \\
  831. 1 & 2 & b2 \\
  832. \end{longtable}
  833. """
  834. )
  835. assert result == expected
  836. class TestToLatexFormatters:
  837. def test_to_latex_with_formatters(self):
  838. df = DataFrame(
  839. {
  840. "datetime64": [
  841. datetime(2016, 1, 1),
  842. datetime(2016, 2, 5),
  843. datetime(2016, 3, 3),
  844. ],
  845. "float": [1.0, 2.0, 3.0],
  846. "int": [1, 2, 3],
  847. "object": [(1, 2), True, False],
  848. }
  849. )
  850. formatters = {
  851. "datetime64": lambda x: x.strftime("%Y-%m"),
  852. "float": lambda x: f"[{x: 4.1f}]",
  853. "int": lambda x: f"0x{x:x}",
  854. "object": lambda x: f"-{x!s}-",
  855. "__index__": lambda x: f"index: {x}",
  856. }
  857. result = df.to_latex(formatters=dict(formatters))
  858. expected = _dedent(
  859. r"""
  860. \begin{tabular}{llrrl}
  861. \toprule
  862. & datetime64 & float & int & object \\
  863. \midrule
  864. index: 0 & 2016-01 & [ 1.0] & 0x1 & -(1, 2)- \\
  865. index: 1 & 2016-02 & [ 2.0] & 0x2 & -True- \\
  866. index: 2 & 2016-03 & [ 3.0] & 0x3 & -False- \\
  867. \bottomrule
  868. \end{tabular}
  869. """
  870. )
  871. assert result == expected
  872. def test_to_latex_float_format_no_fixed_width_3decimals(self):
  873. # GH 21625
  874. df = DataFrame({"x": [0.19999]})
  875. result = df.to_latex(float_format="%.3f")
  876. expected = _dedent(
  877. r"""
  878. \begin{tabular}{lr}
  879. \toprule
  880. & x \\
  881. \midrule
  882. 0 & 0.200 \\
  883. \bottomrule
  884. \end{tabular}
  885. """
  886. )
  887. assert result == expected
  888. def test_to_latex_float_format_no_fixed_width_integer(self):
  889. # GH 22270
  890. df = DataFrame({"x": [100.0]})
  891. result = df.to_latex(float_format="%.0f")
  892. expected = _dedent(
  893. r"""
  894. \begin{tabular}{lr}
  895. \toprule
  896. & x \\
  897. \midrule
  898. 0 & 100 \\
  899. \bottomrule
  900. \end{tabular}
  901. """
  902. )
  903. assert result == expected
  904. @pytest.mark.parametrize("na_rep", ["NaN", "Ted"])
  905. def test_to_latex_na_rep_and_float_format(self, na_rep):
  906. df = DataFrame(
  907. [
  908. ["A", 1.2225],
  909. ["A", None],
  910. ],
  911. columns=["Group", "Data"],
  912. )
  913. result = df.to_latex(na_rep=na_rep, float_format="{:.2f}".format)
  914. expected = _dedent(
  915. rf"""
  916. \begin{{tabular}}{{llr}}
  917. \toprule
  918. & Group & Data \\
  919. \midrule
  920. 0 & A & 1.22 \\
  921. 1 & A & {na_rep} \\
  922. \bottomrule
  923. \end{{tabular}}
  924. """
  925. )
  926. assert result == expected
  927. class TestToLatexMultiindex:
  928. @pytest.fixture
  929. def multiindex_frame(self):
  930. """Multiindex dataframe for testing multirow LaTeX macros."""
  931. yield DataFrame.from_dict(
  932. {
  933. ("c1", 0): Series({x: x for x in range(4)}),
  934. ("c1", 1): Series({x: x + 4 for x in range(4)}),
  935. ("c2", 0): Series({x: x for x in range(4)}),
  936. ("c2", 1): Series({x: x + 4 for x in range(4)}),
  937. ("c3", 0): Series({x: x for x in range(4)}),
  938. }
  939. ).T
  940. @pytest.fixture
  941. def multicolumn_frame(self):
  942. """Multicolumn dataframe for testing multicolumn LaTeX macros."""
  943. yield DataFrame(
  944. {
  945. ("c1", 0): {x: x for x in range(5)},
  946. ("c1", 1): {x: x + 5 for x in range(5)},
  947. ("c2", 0): {x: x for x in range(5)},
  948. ("c2", 1): {x: x + 5 for x in range(5)},
  949. ("c3", 0): {x: x for x in range(5)},
  950. }
  951. )
  952. def test_to_latex_multindex_header(self):
  953. # GH 16718
  954. df = DataFrame({"a": [0], "b": [1], "c": [2], "d": [3]})
  955. df = df.set_index(["a", "b"])
  956. observed = df.to_latex(header=["r1", "r2"], multirow=False)
  957. expected = _dedent(
  958. r"""
  959. \begin{tabular}{llrr}
  960. \toprule
  961. & & r1 & r2 \\
  962. a & b & & \\
  963. \midrule
  964. 0 & 1 & 2 & 3 \\
  965. \bottomrule
  966. \end{tabular}
  967. """
  968. )
  969. assert observed == expected
  970. def test_to_latex_multiindex_empty_name(self):
  971. # GH 18669
  972. mi = pd.MultiIndex.from_product([[1, 2]], names=[""])
  973. df = DataFrame(-1, index=mi, columns=range(4))
  974. observed = df.to_latex()
  975. expected = _dedent(
  976. r"""
  977. \begin{tabular}{lrrrr}
  978. \toprule
  979. & 0 & 1 & 2 & 3 \\
  980. & & & & \\
  981. \midrule
  982. 1 & -1 & -1 & -1 & -1 \\
  983. 2 & -1 & -1 & -1 & -1 \\
  984. \bottomrule
  985. \end{tabular}
  986. """
  987. )
  988. assert observed == expected
  989. def test_to_latex_multiindex_column_tabular(self):
  990. df = DataFrame({("x", "y"): ["a"]})
  991. result = df.to_latex()
  992. expected = _dedent(
  993. r"""
  994. \begin{tabular}{ll}
  995. \toprule
  996. & x \\
  997. & y \\
  998. \midrule
  999. 0 & a \\
  1000. \bottomrule
  1001. \end{tabular}
  1002. """
  1003. )
  1004. assert result == expected
  1005. def test_to_latex_multiindex_small_tabular(self):
  1006. df = DataFrame({("x", "y"): ["a"]}).T
  1007. result = df.to_latex(multirow=False)
  1008. expected = _dedent(
  1009. r"""
  1010. \begin{tabular}{lll}
  1011. \toprule
  1012. & & 0 \\
  1013. \midrule
  1014. x & y & a \\
  1015. \bottomrule
  1016. \end{tabular}
  1017. """
  1018. )
  1019. assert result == expected
  1020. def test_to_latex_multiindex_tabular(self, multiindex_frame):
  1021. result = multiindex_frame.to_latex(multirow=False)
  1022. expected = _dedent(
  1023. r"""
  1024. \begin{tabular}{llrrrr}
  1025. \toprule
  1026. & & 0 & 1 & 2 & 3 \\
  1027. \midrule
  1028. c1 & 0 & 0 & 1 & 2 & 3 \\
  1029. & 1 & 4 & 5 & 6 & 7 \\
  1030. c2 & 0 & 0 & 1 & 2 & 3 \\
  1031. & 1 & 4 & 5 & 6 & 7 \\
  1032. c3 & 0 & 0 & 1 & 2 & 3 \\
  1033. \bottomrule
  1034. \end{tabular}
  1035. """
  1036. )
  1037. assert result == expected
  1038. def test_to_latex_multicolumn_tabular(self, multiindex_frame):
  1039. # GH 14184
  1040. df = multiindex_frame.T
  1041. df.columns.names = ["a", "b"]
  1042. result = df.to_latex(multirow=False)
  1043. expected = _dedent(
  1044. r"""
  1045. \begin{tabular}{lrrrrr}
  1046. \toprule
  1047. a & \multicolumn{2}{r}{c1} & \multicolumn{2}{r}{c2} & c3 \\
  1048. b & 0 & 1 & 0 & 1 & 0 \\
  1049. \midrule
  1050. 0 & 0 & 4 & 0 & 4 & 0 \\
  1051. 1 & 1 & 5 & 1 & 5 & 1 \\
  1052. 2 & 2 & 6 & 2 & 6 & 2 \\
  1053. 3 & 3 & 7 & 3 & 7 & 3 \\
  1054. \bottomrule
  1055. \end{tabular}
  1056. """
  1057. )
  1058. assert result == expected
  1059. def test_to_latex_index_has_name_tabular(self):
  1060. # GH 10660
  1061. df = DataFrame({"a": [0, 0, 1, 1], "b": list("abab"), "c": [1, 2, 3, 4]})
  1062. result = df.set_index(["a", "b"]).to_latex(multirow=False)
  1063. expected = _dedent(
  1064. r"""
  1065. \begin{tabular}{llr}
  1066. \toprule
  1067. & & c \\
  1068. a & b & \\
  1069. \midrule
  1070. 0 & a & 1 \\
  1071. & b & 2 \\
  1072. 1 & a & 3 \\
  1073. & b & 4 \\
  1074. \bottomrule
  1075. \end{tabular}
  1076. """
  1077. )
  1078. assert result == expected
  1079. def test_to_latex_groupby_tabular(self):
  1080. # GH 10660
  1081. df = DataFrame({"a": [0, 0, 1, 1], "b": list("abab"), "c": [1, 2, 3, 4]})
  1082. result = (
  1083. df.groupby("a")
  1084. .describe()
  1085. .to_latex(float_format="{:.1f}".format, escape=True)
  1086. )
  1087. expected = _dedent(
  1088. r"""
  1089. \begin{tabular}{lrrrrrrrr}
  1090. \toprule
  1091. & \multicolumn{8}{r}{c} \\
  1092. & count & mean & std & min & 25\% & 50\% & 75\% & max \\
  1093. a & & & & & & & & \\
  1094. \midrule
  1095. 0 & 2.0 & 1.5 & 0.7 & 1.0 & 1.2 & 1.5 & 1.8 & 2.0 \\
  1096. 1 & 2.0 & 3.5 & 0.7 & 3.0 & 3.2 & 3.5 & 3.8 & 4.0 \\
  1097. \bottomrule
  1098. \end{tabular}
  1099. """
  1100. )
  1101. assert result == expected
  1102. def test_to_latex_multiindex_dupe_level(self):
  1103. # see gh-14484
  1104. #
  1105. # If an index is repeated in subsequent rows, it should be
  1106. # replaced with a blank in the created table. This should
  1107. # ONLY happen if all higher order indices (to the left) are
  1108. # equal too. In this test, 'c' has to be printed both times
  1109. # because the higher order index 'A' != 'B'.
  1110. df = DataFrame(
  1111. index=pd.MultiIndex.from_tuples([("A", "c"), ("B", "c")]), columns=["col"]
  1112. )
  1113. result = df.to_latex(multirow=False)
  1114. expected = _dedent(
  1115. r"""
  1116. \begin{tabular}{lll}
  1117. \toprule
  1118. & & col \\
  1119. \midrule
  1120. A & c & NaN \\
  1121. B & c & NaN \\
  1122. \bottomrule
  1123. \end{tabular}
  1124. """
  1125. )
  1126. assert result == expected
  1127. def test_to_latex_multicolumn_default(self, multicolumn_frame):
  1128. result = multicolumn_frame.to_latex()
  1129. expected = _dedent(
  1130. r"""
  1131. \begin{tabular}{lrrrrr}
  1132. \toprule
  1133. & \multicolumn{2}{r}{c1} & \multicolumn{2}{r}{c2} & c3 \\
  1134. & 0 & 1 & 0 & 1 & 0 \\
  1135. \midrule
  1136. 0 & 0 & 5 & 0 & 5 & 0 \\
  1137. 1 & 1 & 6 & 1 & 6 & 1 \\
  1138. 2 & 2 & 7 & 2 & 7 & 2 \\
  1139. 3 & 3 & 8 & 3 & 8 & 3 \\
  1140. 4 & 4 & 9 & 4 & 9 & 4 \\
  1141. \bottomrule
  1142. \end{tabular}
  1143. """
  1144. )
  1145. assert result == expected
  1146. def test_to_latex_multicolumn_false(self, multicolumn_frame):
  1147. result = multicolumn_frame.to_latex(multicolumn=False, multicolumn_format="l")
  1148. expected = _dedent(
  1149. r"""
  1150. \begin{tabular}{lrrrrr}
  1151. \toprule
  1152. & c1 & & c2 & & c3 \\
  1153. & 0 & 1 & 0 & 1 & 0 \\
  1154. \midrule
  1155. 0 & 0 & 5 & 0 & 5 & 0 \\
  1156. 1 & 1 & 6 & 1 & 6 & 1 \\
  1157. 2 & 2 & 7 & 2 & 7 & 2 \\
  1158. 3 & 3 & 8 & 3 & 8 & 3 \\
  1159. 4 & 4 & 9 & 4 & 9 & 4 \\
  1160. \bottomrule
  1161. \end{tabular}
  1162. """
  1163. )
  1164. assert result == expected
  1165. def test_to_latex_multirow_true(self, multicolumn_frame):
  1166. result = multicolumn_frame.T.to_latex(multirow=True)
  1167. expected = _dedent(
  1168. r"""
  1169. \begin{tabular}{llrrrrr}
  1170. \toprule
  1171. & & 0 & 1 & 2 & 3 & 4 \\
  1172. \midrule
  1173. \multirow[t]{2}{*}{c1} & 0 & 0 & 1 & 2 & 3 & 4 \\
  1174. & 1 & 5 & 6 & 7 & 8 & 9 \\
  1175. \cline{1-7}
  1176. \multirow[t]{2}{*}{c2} & 0 & 0 & 1 & 2 & 3 & 4 \\
  1177. & 1 & 5 & 6 & 7 & 8 & 9 \\
  1178. \cline{1-7}
  1179. c3 & 0 & 0 & 1 & 2 & 3 & 4 \\
  1180. \cline{1-7}
  1181. \bottomrule
  1182. \end{tabular}
  1183. """
  1184. )
  1185. assert result == expected
  1186. def test_to_latex_multicolumnrow_with_multicol_format(self, multicolumn_frame):
  1187. multicolumn_frame.index = multicolumn_frame.T.index
  1188. result = multicolumn_frame.T.to_latex(
  1189. multirow=True,
  1190. multicolumn=True,
  1191. multicolumn_format="c",
  1192. )
  1193. expected = _dedent(
  1194. r"""
  1195. \begin{tabular}{llrrrrr}
  1196. \toprule
  1197. & & \multicolumn{2}{c}{c1} & \multicolumn{2}{c}{c2} & c3 \\
  1198. & & 0 & 1 & 0 & 1 & 0 \\
  1199. \midrule
  1200. \multirow[t]{2}{*}{c1} & 0 & 0 & 1 & 2 & 3 & 4 \\
  1201. & 1 & 5 & 6 & 7 & 8 & 9 \\
  1202. \cline{1-7}
  1203. \multirow[t]{2}{*}{c2} & 0 & 0 & 1 & 2 & 3 & 4 \\
  1204. & 1 & 5 & 6 & 7 & 8 & 9 \\
  1205. \cline{1-7}
  1206. c3 & 0 & 0 & 1 & 2 & 3 & 4 \\
  1207. \cline{1-7}
  1208. \bottomrule
  1209. \end{tabular}
  1210. """
  1211. )
  1212. assert result == expected
  1213. @pytest.mark.parametrize("name0", [None, "named0"])
  1214. @pytest.mark.parametrize("name1", [None, "named1"])
  1215. @pytest.mark.parametrize("axes", [[0], [1], [0, 1]])
  1216. def test_to_latex_multiindex_names(self, name0, name1, axes):
  1217. # GH 18667
  1218. names = [name0, name1]
  1219. mi = pd.MultiIndex.from_product([[1, 2], [3, 4]])
  1220. df = DataFrame(-1, index=mi.copy(), columns=mi.copy())
  1221. for idx in axes:
  1222. df.axes[idx].names = names
  1223. idx_names = tuple(n or "" for n in names)
  1224. idx_names_row = (
  1225. f"{idx_names[0]} & {idx_names[1]} & & & & \\\\\n"
  1226. if (0 in axes and any(names))
  1227. else ""
  1228. )
  1229. col_names = [n if (bool(n) and 1 in axes) else "" for n in names]
  1230. observed = df.to_latex(multirow=False)
  1231. # pylint: disable-next=consider-using-f-string
  1232. expected = r"""\begin{tabular}{llrrrr}
  1233. \toprule
  1234. & %s & \multicolumn{2}{r}{1} & \multicolumn{2}{r}{2} \\
  1235. & %s & 3 & 4 & 3 & 4 \\
  1236. %s\midrule
  1237. 1 & 3 & -1 & -1 & -1 & -1 \\
  1238. & 4 & -1 & -1 & -1 & -1 \\
  1239. 2 & 3 & -1 & -1 & -1 & -1 \\
  1240. & 4 & -1 & -1 & -1 & -1 \\
  1241. \bottomrule
  1242. \end{tabular}
  1243. """ % tuple(
  1244. list(col_names) + [idx_names_row]
  1245. )
  1246. assert observed == expected
  1247. @pytest.mark.parametrize("one_row", [True, False])
  1248. def test_to_latex_multiindex_nans(self, one_row):
  1249. # GH 14249
  1250. df = DataFrame({"a": [None, 1], "b": [2, 3], "c": [4, 5]})
  1251. if one_row:
  1252. df = df.iloc[[0]]
  1253. observed = df.set_index(["a", "b"]).to_latex(multirow=False)
  1254. expected = _dedent(
  1255. r"""
  1256. \begin{tabular}{llr}
  1257. \toprule
  1258. & & c \\
  1259. a & b & \\
  1260. \midrule
  1261. NaN & 2 & 4 \\
  1262. """
  1263. )
  1264. if not one_row:
  1265. expected += r"""1.000000 & 3 & 5 \\
  1266. """
  1267. expected += r"""\bottomrule
  1268. \end{tabular}
  1269. """
  1270. assert observed == expected
  1271. def test_to_latex_non_string_index(self):
  1272. # GH 19981
  1273. df = DataFrame([[1, 2, 3]] * 2).set_index([0, 1])
  1274. result = df.to_latex(multirow=False)
  1275. expected = _dedent(
  1276. r"""
  1277. \begin{tabular}{llr}
  1278. \toprule
  1279. & & 2 \\
  1280. 0 & 1 & \\
  1281. \midrule
  1282. 1 & 2 & 3 \\
  1283. & 2 & 3 \\
  1284. \bottomrule
  1285. \end{tabular}
  1286. """
  1287. )
  1288. assert result == expected
  1289. def test_to_latex_multiindex_multirow(self):
  1290. # GH 16719
  1291. mi = pd.MultiIndex.from_product(
  1292. [[0.0, 1.0], [3.0, 2.0, 1.0], ["0", "1"]], names=["i", "val0", "val1"]
  1293. )
  1294. df = DataFrame(index=mi)
  1295. result = df.to_latex(multirow=True, escape=False)
  1296. expected = _dedent(
  1297. r"""
  1298. \begin{tabular}{lll}
  1299. \toprule
  1300. i & val0 & val1 \\
  1301. \midrule
  1302. \multirow[t]{6}{*}{0.000000} & \multirow[t]{2}{*}{3.000000} & 0 \\
  1303. & & 1 \\
  1304. \cline{2-3}
  1305. & \multirow[t]{2}{*}{2.000000} & 0 \\
  1306. & & 1 \\
  1307. \cline{2-3}
  1308. & \multirow[t]{2}{*}{1.000000} & 0 \\
  1309. & & 1 \\
  1310. \cline{1-3} \cline{2-3}
  1311. \multirow[t]{6}{*}{1.000000} & \multirow[t]{2}{*}{3.000000} & 0 \\
  1312. & & 1 \\
  1313. \cline{2-3}
  1314. & \multirow[t]{2}{*}{2.000000} & 0 \\
  1315. & & 1 \\
  1316. \cline{2-3}
  1317. & \multirow[t]{2}{*}{1.000000} & 0 \\
  1318. & & 1 \\
  1319. \cline{1-3} \cline{2-3}
  1320. \bottomrule
  1321. \end{tabular}
  1322. """
  1323. )
  1324. assert result == expected
  1325. class TestTableBuilder:
  1326. @pytest.fixture
  1327. def dataframe(self):
  1328. return DataFrame({"a": [1, 2], "b": ["b1", "b2"]})
  1329. @pytest.fixture
  1330. def table_builder(self, dataframe):
  1331. return RegularTableBuilder(formatter=DataFrameFormatter(dataframe))
  1332. def test_create_row_iterator(self, table_builder):
  1333. iterator = table_builder._create_row_iterator(over="header")
  1334. assert isinstance(iterator, RowHeaderIterator)
  1335. def test_create_body_iterator(self, table_builder):
  1336. iterator = table_builder._create_row_iterator(over="body")
  1337. assert isinstance(iterator, RowBodyIterator)
  1338. def test_create_body_wrong_kwarg_raises(self, table_builder):
  1339. with pytest.raises(ValueError, match="must be either 'header' or 'body'"):
  1340. table_builder._create_row_iterator(over="SOMETHING BAD")
  1341. class TestRowStringConverter:
  1342. @pytest.mark.parametrize(
  1343. "row_num, expected",
  1344. [
  1345. (0, r"{} & Design & ratio & xy \\"),
  1346. (1, r"0 & 1 & 4 & 10 \\"),
  1347. (2, r"1 & 2 & 5 & 11 \\"),
  1348. ],
  1349. )
  1350. def test_get_strrow_normal_without_escape(self, row_num, expected):
  1351. df = DataFrame({r"Design": [1, 2, 3], r"ratio": [4, 5, 6], r"xy": [10, 11, 12]})
  1352. row_string_converter = RowStringConverter(
  1353. formatter=DataFrameFormatter(df, escape=True),
  1354. )
  1355. assert row_string_converter.get_strrow(row_num=row_num) == expected
  1356. @pytest.mark.parametrize(
  1357. "row_num, expected",
  1358. [
  1359. (0, r"{} & Design \# & ratio, \% & x\&y \\"),
  1360. (1, r"0 & 1 & 4 & 10 \\"),
  1361. (2, r"1 & 2 & 5 & 11 \\"),
  1362. ],
  1363. )
  1364. def test_get_strrow_normal_with_escape(self, row_num, expected):
  1365. df = DataFrame(
  1366. {r"Design #": [1, 2, 3], r"ratio, %": [4, 5, 6], r"x&y": [10, 11, 12]}
  1367. )
  1368. row_string_converter = RowStringConverter(
  1369. formatter=DataFrameFormatter(df, escape=True),
  1370. )
  1371. assert row_string_converter.get_strrow(row_num=row_num) == expected
  1372. @pytest.mark.parametrize(
  1373. "row_num, expected",
  1374. [
  1375. (0, r"{} & \multicolumn{2}{r}{c1} & \multicolumn{2}{r}{c2} & c3 \\"),
  1376. (1, r"{} & 0 & 1 & 0 & 1 & 0 \\"),
  1377. (2, r"0 & 0 & 5 & 0 & 5 & 0 \\"),
  1378. ],
  1379. )
  1380. def test_get_strrow_multindex_multicolumn(self, row_num, expected):
  1381. df = DataFrame(
  1382. {
  1383. ("c1", 0): {x: x for x in range(5)},
  1384. ("c1", 1): {x: x + 5 for x in range(5)},
  1385. ("c2", 0): {x: x for x in range(5)},
  1386. ("c2", 1): {x: x + 5 for x in range(5)},
  1387. ("c3", 0): {x: x for x in range(5)},
  1388. }
  1389. )
  1390. row_string_converter = RowStringConverter(
  1391. formatter=DataFrameFormatter(df),
  1392. multicolumn=True,
  1393. multicolumn_format="r",
  1394. multirow=True,
  1395. )
  1396. assert row_string_converter.get_strrow(row_num=row_num) == expected