@@ -646,13 +646,17 @@ def _join_multiline(self, *strcols):
646
646
st = ed
647
647
return '\n \n ' .join (str_lst )
648
648
649
- def to_latex (self , column_format = None , longtable = False , encoding = None ):
649
+ def to_latex (self , column_format = None , longtable = False , encoding = None ,
650
+ multicolumn = False , multicolumn_format = None , multirow = False ):
650
651
"""
651
652
Render a DataFrame to a LaTeX tabular/longtable environment output.
652
653
"""
653
654
654
655
latex_renderer = LatexFormatter (self , column_format = column_format ,
655
- longtable = longtable )
656
+ longtable = longtable ,
657
+ multicolumn = multicolumn ,
658
+ multicolumn_format = multicolumn_format ,
659
+ multirow = multirow )
656
660
657
661
if encoding is None :
658
662
encoding = 'ascii' if compat .PY2 else 'utf-8'
@@ -820,11 +824,15 @@ class LatexFormatter(TableFormatter):
820
824
HTMLFormatter
821
825
"""
822
826
823
- def __init__ (self , formatter , column_format = None , longtable = False ):
827
+ def __init__ (self , formatter , column_format = None , longtable = False ,
828
+ multicolumn = False , multicolumn_format = None , multirow = False ):
824
829
self .fmt = formatter
825
830
self .frame = self .fmt .frame
826
831
self .column_format = column_format
827
832
self .longtable = longtable
833
+ self .multicolumn = multicolumn
834
+ self .multicolumn_format = multicolumn_format
835
+ self .multirow = multirow
828
836
829
837
def write_result (self , buf ):
830
838
"""
@@ -846,14 +854,21 @@ def get_col_type(dtype):
846
854
else :
847
855
return 'l'
848
856
857
+ # reestablish the MultiIndex that has been joined by _to_str_column
849
858
if self .fmt .index and isinstance (self .frame .index , MultiIndex ):
850
859
clevels = self .frame .columns .nlevels
851
860
strcols .pop (0 )
852
861
name = any (self .frame .index .names )
862
+ cname = any (self .frame .columns .names )
863
+ lastcol = self .frame .index .nlevels - 1
853
864
for i , lev in enumerate (self .frame .index .levels ):
854
865
lev2 = lev .format ()
855
866
blank = ' ' * len (lev2 [0 ])
856
- lev3 = [blank ] * clevels
867
+ # display column names in last index-column
868
+ if cname and i == lastcol :
869
+ lev3 = [x if x else '{}' for x in self .frame .columns .names ]
870
+ else :
871
+ lev3 = [blank ] * clevels
857
872
if name :
858
873
lev3 .append (lev .name )
859
874
for level_idx , group in itertools .groupby (
@@ -873,6 +888,13 @@ def get_col_type(dtype):
873
888
compat .string_types ): # pragma: no cover
874
889
raise AssertionError ('column_format must be str or unicode, not %s'
875
890
% type (column_format ))
891
+ multicolumn_format = self .multicolumn_format
892
+ if multicolumn_format is None :
893
+ multicolumn_format = get_option ("display.latex.multicolumn_format" )
894
+ elif not isinstance (multicolumn_format ,
895
+ compat .string_types ): # pragma: no cover
896
+ raise AssertionError ('multicolumn_format must be str or unicode,'
897
+ ' not %s' % type (multicolumn_format ))
876
898
877
899
if not self .longtable :
878
900
buf .write ('\\ begin{tabular}{%s}\n ' % column_format )
@@ -881,10 +903,15 @@ def get_col_type(dtype):
881
903
buf .write ('\\ begin{longtable}{%s}\n ' % column_format )
882
904
buf .write ('\\ toprule\n ' )
883
905
884
- nlevels = self .frame .columns .nlevels
906
+ ilevels = self .frame .index .nlevels
907
+ clevels = self .frame .columns .nlevels
908
+ nlevels = clevels
885
909
if any (self .frame .index .names ):
886
910
nlevels += 1
887
- for i , row in enumerate (zip (* strcols )):
911
+ strrows = list (zip (* strcols ))
912
+ clinebuf = []
913
+
914
+ for i , row in enumerate (strrows ):
888
915
if i == nlevels and self .fmt .header :
889
916
buf .write ('\\ midrule\n ' ) # End of header
890
917
if self .longtable :
@@ -906,8 +933,59 @@ def get_col_type(dtype):
906
933
if x else '{}' ) for x in row ]
907
934
else :
908
935
crow = [x if x else '{}' for x in row ]
936
+ if i < clevels and self .fmt .header and self .multicolumn :
937
+ # sum up columns to multicolumns
938
+ row2 = list (crow [:ilevels ])
939
+ ncol = 1
940
+ coltext = ''
941
+
942
+ def append_col ():
943
+ # write multicolumn if needed
944
+ if ncol > 1 :
945
+ row2 .append ('\\ multicolumn{{{0:d}}}{{{1:s}}}{{{2:s}}}'
946
+ .format (ncol , multicolumn_format ,
947
+ coltext .strip ()))
948
+ # don't modify where not needed
949
+ else :
950
+ row2 .append (coltext )
951
+ for c in crow [ilevels :]:
952
+ if c .strip (): # if next col has text, write the previous
953
+ if coltext :
954
+ append_col ()
955
+ coltext = c
956
+ ncol = 1
957
+ else : # if not, add it to the previous multicolumn
958
+ ncol += 1
959
+ if coltext : # write last column name
960
+ append_col ()
961
+ crow = row2
962
+ if i >= nlevels and self .fmt .index and self .multirow :
963
+ # perform look-ahead to determine which rows can be joined
964
+ for j in range (ilevels ):
965
+ if crow [j ].strip ():
966
+ nrow = 1
967
+ for r in strrows [i + 1 :]:
968
+ if not r [j ].strip ():
969
+ nrow += 1
970
+ else :
971
+ break
972
+ if nrow > 1 :
973
+ # overwrite non-multirow entry
974
+ crow [j ] = '\\ multirow{{{0:d}}}{{*}}{{{1:s}}}' \
975
+ .format (nrow , crow [j ].strip ())
976
+ # save when to end the current block with \cline
977
+ clinebuf .append ([nrow , j + 1 ])
909
978
buf .write (' & ' .join (crow ))
910
979
buf .write (' \\ \\ \n ' )
980
+ if self .multirow and i < len (strrows ) - 1 :
981
+ # during main block, check if sub-block transition takes place
982
+ # print \cline to distinguish \multirow-areas
983
+ for cl in clinebuf :
984
+ cl [0 ] -= 1
985
+ if cl [0 ] == 0 :
986
+ buf .write ('\cline{{{0:d}-{1:d}}}\n ' .format (cl [1 ],
987
+ len (strcols )))
988
+ clinebuf = [x for x in clinebuf if x [0 ]]
911
989
912
990
if not self .longtable :
913
991
buf .write ('\\ bottomrule\n ' )
0 commit comments