@@ -1213,7 +1213,7 @@ def __init__(self, row, col, val,
1213
1213
"right" : "thin" ,
1214
1214
"bottom" : "thin" ,
1215
1215
"left" : "thin" },
1216
- "alignment" : {"horizontal" : "center" }}
1216
+ "alignment" : {"horizontal" : "center" , "vertical" : "top" }}
1217
1217
1218
1218
1219
1219
class ExcelFormatter (object ):
@@ -1237,10 +1237,12 @@ class ExcelFormatter(object):
1237
1237
Column label for index column(s) if desired. If None is given, and
1238
1238
`header` and `index` are True, then the index names are used. A
1239
1239
sequence should be given if the DataFrame uses MultiIndex.
1240
+ merge_cells : boolean, default False
1241
+ Format MultiIndex and Hierarchical Rows as merged cells.
1240
1242
"""
1241
1243
1242
1244
def __init__ (self , df , na_rep = '' , float_format = None , cols = None ,
1243
- header = True , index = True , index_label = None ):
1245
+ header = True , index = True , index_label = None , merge_cells = False ):
1244
1246
self .df = df
1245
1247
self .rowcounter = 0
1246
1248
self .na_rep = na_rep
@@ -1251,6 +1253,7 @@ def __init__(self, df, na_rep='', float_format=None, cols=None,
1251
1253
self .index = index
1252
1254
self .index_label = index_label
1253
1255
self .header = header
1256
+ self .merge_cells = merge_cells
1254
1257
1255
1258
def _format_value (self , val ):
1256
1259
if lib .checknull (val ):
@@ -1264,29 +1267,44 @@ def _format_header_mi(self):
1264
1267
if not (has_aliases or self .header ):
1265
1268
return
1266
1269
1267
- levels = self .columns .format (sparsify = True , adjoin = False ,
1268
- names = False )
1269
- # level_lenghts = _get_level_lengths(levels)
1270
- coloffset = 1
1271
- if isinstance (self .df .index , MultiIndex ):
1272
- coloffset = len (self .df .index [0 ])
1273
-
1274
- # for lnum, (records, values) in enumerate(zip(level_lenghts,
1275
- # levels)):
1276
- # name = self.columns.names[lnum]
1277
- # yield ExcelCell(lnum, coloffset, name, header_style)
1278
- # for i in records:
1279
- # if records[i] > 1:
1280
- # yield ExcelCell(lnum,coloffset + i + 1, values[i],
1281
- # header_style, lnum, coloffset + i + records[i])
1282
- # else:
1283
- # yield ExcelCell(lnum, coloffset + i + 1, values[i], header_style)
1284
-
1285
- # self.rowcounter = lnum
1270
+ columns = self .columns
1271
+ level_strs = columns .format (sparsify = True , adjoin = False , names = False )
1272
+ level_lengths = _get_level_lengths (level_strs )
1273
+ coloffset = 0
1286
1274
lnum = 0
1287
- for i , values in enumerate (zip (* levels )):
1288
- v = "." .join (map (com .pprint_thing , values ))
1289
- yield ExcelCell (lnum , coloffset + i , v , header_style )
1275
+
1276
+ if isinstance (self .df .index , MultiIndex ):
1277
+ coloffset = len (self .df .index [0 ]) - 1
1278
+
1279
+ if self .merge_cells :
1280
+ # Format multi-index as a merged cells.
1281
+ for lnum in range (len (level_lengths )):
1282
+ name = columns .names [lnum ]
1283
+ yield ExcelCell (lnum , coloffset , name , header_style )
1284
+
1285
+ for lnum , (spans , levels , labels ) in enumerate (zip (level_lengths ,
1286
+ columns .levels ,
1287
+ columns .labels )
1288
+ ):
1289
+ values = levels .take (labels )
1290
+ for i in spans :
1291
+ if spans [i ] > 1 :
1292
+ yield ExcelCell (lnum ,
1293
+ coloffset + i + 1 ,
1294
+ values [i ],
1295
+ header_style ,
1296
+ lnum ,
1297
+ coloffset + i + spans [i ])
1298
+ else :
1299
+ yield ExcelCell (lnum ,
1300
+ coloffset + i + 1 ,
1301
+ values [i ],
1302
+ header_style )
1303
+ else :
1304
+ # Format in legacy format with dots to indicate levels.
1305
+ for i , values in enumerate (zip (* level_strs )):
1306
+ v = "." .join (map (com .pprint_thing , values ))
1307
+ yield ExcelCell (lnum , coloffset + i + 1 , v , header_style )
1290
1308
1291
1309
self .rowcounter = lnum
1292
1310
@@ -1354,14 +1372,17 @@ def _format_regular_rows(self):
1354
1372
index_label = self .df .index .names [0 ]
1355
1373
1356
1374
if index_label and self .header is not False :
1357
- # add to same level as column names
1358
- # if isinstance(self.df.columns, MultiIndex):
1359
- # yield ExcelCell(self.rowcounter, 0,
1360
- # index_label, header_style)
1361
- # self.rowcounter += 1
1362
- # else:
1363
- yield ExcelCell (self .rowcounter - 1 , 0 ,
1364
- index_label , header_style )
1375
+ if self .merge_cells :
1376
+ yield ExcelCell (self .rowcounter ,
1377
+ 0 ,
1378
+ index_label ,
1379
+ header_style )
1380
+ self .rowcounter += 1
1381
+ else :
1382
+ yield ExcelCell (self .rowcounter - 1 ,
1383
+ 0 ,
1384
+ index_label ,
1385
+ header_style )
1365
1386
1366
1387
# write index_values
1367
1388
index_values = self .df .index
@@ -1383,7 +1404,7 @@ def _format_hierarchical_rows(self):
1383
1404
self .rowcounter += 1
1384
1405
1385
1406
gcolidx = 0
1386
- # output index and index_label?
1407
+
1387
1408
if self .index :
1388
1409
index_labels = self .df .index .names
1389
1410
# check for aliases
@@ -1394,29 +1415,60 @@ def _format_hierarchical_rows(self):
1394
1415
# if index labels are not empty go ahead and dump
1395
1416
if (any (x is not None for x in index_labels )
1396
1417
and self .header is not False ):
1397
- # if isinstance(self.df.columns, MultiIndex):
1398
- # self.rowcounter += 1
1399
- # else:
1400
- self . rowcounter -= 1
1418
+
1419
+ if not self .merge_cells :
1420
+ self . rowcounter -= 1
1421
+
1401
1422
for cidx , name in enumerate (index_labels ):
1402
- yield ExcelCell (self .rowcounter , cidx ,
1403
- name , header_style )
1423
+ yield ExcelCell (self .rowcounter ,
1424
+ cidx ,
1425
+ name ,
1426
+ header_style )
1404
1427
self .rowcounter += 1
1405
1428
1406
- for indexcolvals in zip (* self .df .index ):
1407
- for idx , indexcolval in enumerate (indexcolvals ):
1408
- yield ExcelCell (self .rowcounter + idx , gcolidx ,
1409
- indexcolval , header_style )
1410
- gcolidx += 1
1429
+ if self .merge_cells :
1430
+ # Format hierarchical rows as merged cells.
1431
+ level_strs = self .df .index .format (sparsify = True , adjoin = False ,
1432
+ names = False )
1433
+ level_lengths = _get_level_lengths (level_strs )
1434
+
1435
+ for spans , levels , labels in zip (level_lengths ,
1436
+ self .df .index .levels ,
1437
+ self .df .index .labels ):
1438
+ values = levels .take (labels )
1439
+ for i in spans :
1440
+ if spans [i ] > 1 :
1441
+ yield ExcelCell (self .rowcounter + i ,
1442
+ gcolidx ,
1443
+ values [i ],
1444
+ header_style ,
1445
+ self .rowcounter + i + spans [i ] - 1 ,
1446
+ gcolidx )
1447
+ else :
1448
+ yield ExcelCell (self .rowcounter + i ,
1449
+ gcolidx ,
1450
+ values [i ],
1451
+ header_style )
1452
+ gcolidx += 1
1453
+
1454
+ else :
1455
+ # Format hierarchical rows with non-merged values.
1456
+ for indexcolvals in zip (* self .df .index ):
1457
+ for idx , indexcolval in enumerate (indexcolvals ):
1458
+ yield ExcelCell (self .rowcounter + idx ,
1459
+ gcolidx ,
1460
+ indexcolval ,
1461
+ header_style )
1462
+ gcolidx += 1
1411
1463
1412
1464
for colidx in range (len (self .columns )):
1413
1465
series = self .df .iloc [:, colidx ]
1414
1466
for i , val in enumerate (series ):
1415
1467
yield ExcelCell (self .rowcounter + i , gcolidx + colidx , val )
1416
1468
1417
1469
def get_formatted_cells (self ):
1418
- for cell in itertools .chain (self ._format_header (), self . _format_body ()
1419
- ):
1470
+ for cell in itertools .chain (self ._format_header (),
1471
+ self . _format_body () ):
1420
1472
cell .val = self ._format_value (cell .val )
1421
1473
yield cell
1422
1474
0 commit comments