|
22 | 22 | from pandas._config import get_option
|
23 | 23 |
|
24 | 24 | from pandas._libs import lib
|
25 |
| -from pandas._typing import Level |
| 25 | +from pandas._typing import ( |
| 26 | + Axis, |
| 27 | + Level, |
| 28 | +) |
26 | 29 | from pandas.compat._optional import import_optional_dependency
|
27 | 30 |
|
28 | 31 | from pandas.core.dtypes.common import (
|
@@ -1339,6 +1342,164 @@ def format_index(
|
1339 | 1342 |
|
1340 | 1343 | return self
|
1341 | 1344 |
|
| 1345 | + def relabel_index( |
| 1346 | + self, |
| 1347 | + labels: Sequence | Index, |
| 1348 | + axis: Axis = 0, |
| 1349 | + level: Level | list[Level] | None = None, |
| 1350 | + ) -> StylerRenderer: |
| 1351 | + r""" |
| 1352 | + Relabel the index, or column header, keys to display a set of specified values. |
| 1353 | +
|
| 1354 | + .. versionadded:: 1.5.0 |
| 1355 | +
|
| 1356 | + Parameters |
| 1357 | + ---------- |
| 1358 | + labels : list-like or Index |
| 1359 | + New labels to display. Must have same length as the underlying values not |
| 1360 | + hidden. |
| 1361 | + axis : {"index", 0, "columns", 1} |
| 1362 | + Apply to the index or columns. |
| 1363 | + level : int, str, list, optional |
| 1364 | + The level(s) over which to apply the new labels. If `None` will apply |
| 1365 | + to all levels of an Index or MultiIndex which are not hidden. |
| 1366 | +
|
| 1367 | + Returns |
| 1368 | + ------- |
| 1369 | + self : Styler |
| 1370 | +
|
| 1371 | + See Also |
| 1372 | + -------- |
| 1373 | + Styler.format_index: Format the text display value of index or column headers. |
| 1374 | + Styler.hide: Hide the index, column headers, or specified data from display. |
| 1375 | +
|
| 1376 | + Notes |
| 1377 | + ----- |
| 1378 | + As part of Styler, this method allows the display of an index to be |
| 1379 | + completely user-specified without affecting the underlying DataFrame data, |
| 1380 | + index, or column headers. This means that the flexibility of indexing is |
| 1381 | + maintained whilst the final display is customisable. |
| 1382 | +
|
| 1383 | + Since Styler is designed to be progressively constructed with method chaining, |
| 1384 | + this method is adapted to react to the **currently specified hidden elements**. |
| 1385 | + This is useful because it means one does not have to specify all the new |
| 1386 | + labels if the majority of an index, or column headers, have already been hidden. |
| 1387 | + The following produce equivalent display (note the length of ``labels`` in |
| 1388 | + each case). |
| 1389 | +
|
| 1390 | + .. code-block:: python |
| 1391 | +
|
| 1392 | + # relabel first, then hide |
| 1393 | + df = pd.DataFrame({"col": ["a", "b", "c"]}) |
| 1394 | + df.style.relabel_index(["A", "B", "C"]).hide([0,1]) |
| 1395 | + # hide first, then relabel |
| 1396 | + df = pd.DataFrame({"col": ["a", "b", "c"]}) |
| 1397 | + df.style.hide([0,1]).relabel_index(["C"]) |
| 1398 | +
|
| 1399 | + This method should be used, rather than :meth:`Styler.format_index`, in one of |
| 1400 | + the following cases (see examples): |
| 1401 | +
|
| 1402 | + - A specified set of labels are required which are not a function of the |
| 1403 | + underlying index keys. |
| 1404 | + - The function of the underlying index keys requires a counter variable, |
| 1405 | + such as those available upon enumeration. |
| 1406 | +
|
| 1407 | + Examples |
| 1408 | + -------- |
| 1409 | + Basic use |
| 1410 | +
|
| 1411 | + >>> df = pd.DataFrame({"col": ["a", "b", "c"]}) |
| 1412 | + >>> df.style.relabel_index(["A", "B", "C"]) # doctest: +SKIP |
| 1413 | + col |
| 1414 | + A a |
| 1415 | + B b |
| 1416 | + C c |
| 1417 | +
|
| 1418 | + Chaining with pre-hidden elements |
| 1419 | +
|
| 1420 | + >>> df.style.hide([0,1]).relabel_index(["C"]) # doctest: +SKIP |
| 1421 | + col |
| 1422 | + C c |
| 1423 | +
|
| 1424 | + Using a MultiIndex |
| 1425 | +
|
| 1426 | + >>> midx = pd.MultiIndex.from_product([[0, 1], [0, 1], [0, 1]]) |
| 1427 | + >>> df = pd.DataFrame({"col": list(range(8))}, index=midx) |
| 1428 | + >>> styler = df.style # doctest: +SKIP |
| 1429 | + col |
| 1430 | + 0 0 0 0 |
| 1431 | + 1 1 |
| 1432 | + 1 0 2 |
| 1433 | + 1 3 |
| 1434 | + 1 0 0 4 |
| 1435 | + 1 5 |
| 1436 | + 1 0 6 |
| 1437 | + 1 7 |
| 1438 | + >>> styler.hide((midx.get_level_values(0)==0)|(midx.get_level_values(1)==0)) |
| 1439 | + ... # doctest: +SKIP |
| 1440 | + >>> styler.hide(level=[0,1]) # doctest: +SKIP |
| 1441 | + >>> styler.relabel_index(["binary6", "binary7"]) # doctest: +SKIP |
| 1442 | + col |
| 1443 | + binary6 6 |
| 1444 | + binary7 7 |
| 1445 | +
|
| 1446 | + We can also achieve the above by indexing first and then re-labeling |
| 1447 | +
|
| 1448 | + >>> styler = df.loc[[(1,1,0), (1,1,1)]].style |
| 1449 | + >>> styler.hide(level=[0,1]).relabel_index(["binary6", "binary7"]) |
| 1450 | + ... # doctest: +SKIP |
| 1451 | + col |
| 1452 | + binary6 6 |
| 1453 | + binary7 7 |
| 1454 | +
|
| 1455 | + Defining a formatting function which uses an enumeration counter. Also note |
| 1456 | + that the value of the index key is passed in the case of string labels so it |
| 1457 | + can also be inserted into the label, using curly brackets (or double curly |
| 1458 | + brackets if the string if pre-formatted), |
| 1459 | +
|
| 1460 | + >>> df = pd.DataFrame({"samples": np.random.rand(10)}) |
| 1461 | + >>> styler = df.loc[np.random.randint(0,10,3)].style |
| 1462 | + >>> styler.relabel_index([f"sample{i+1} ({{}})" for i in range(3)]) |
| 1463 | + ... # doctest: +SKIP |
| 1464 | + samples |
| 1465 | + sample1 (5) 0.315811 |
| 1466 | + sample2 (0) 0.495941 |
| 1467 | + sample3 (2) 0.067946 |
| 1468 | + """ |
| 1469 | + axis = self.data._get_axis_number(axis) |
| 1470 | + if axis == 0: |
| 1471 | + display_funcs_, obj = self._display_funcs_index, self.index |
| 1472 | + hidden_labels, hidden_lvls = self.hidden_rows, self.hide_index_ |
| 1473 | + else: |
| 1474 | + display_funcs_, obj = self._display_funcs_columns, self.columns |
| 1475 | + hidden_labels, hidden_lvls = self.hidden_columns, self.hide_columns_ |
| 1476 | + visible_len = len(obj) - len(set(hidden_labels)) |
| 1477 | + if len(labels) != visible_len: |
| 1478 | + raise ValueError( |
| 1479 | + "``labels`` must be of length equal to the number of " |
| 1480 | + f"visible labels along ``axis`` ({visible_len})." |
| 1481 | + ) |
| 1482 | + |
| 1483 | + if level is None: |
| 1484 | + level = [i for i in range(obj.nlevels) if not hidden_lvls[i]] |
| 1485 | + levels_ = refactor_levels(level, obj) |
| 1486 | + |
| 1487 | + def alias_(x, value): |
| 1488 | + if isinstance(value, str): |
| 1489 | + return value.format(x) |
| 1490 | + return value |
| 1491 | + |
| 1492 | + for ai, i in enumerate([i for i in range(len(obj)) if i not in hidden_labels]): |
| 1493 | + if len(levels_) == 1: |
| 1494 | + idx = (i, levels_[0]) if axis == 0 else (levels_[0], i) |
| 1495 | + display_funcs_[idx] = partial(alias_, value=labels[ai]) |
| 1496 | + else: |
| 1497 | + for aj, lvl in enumerate(levels_): |
| 1498 | + idx = (i, lvl) if axis == 0 else (lvl, i) |
| 1499 | + display_funcs_[idx] = partial(alias_, value=labels[ai][aj]) |
| 1500 | + |
| 1501 | + return self |
| 1502 | + |
1342 | 1503 |
|
1343 | 1504 | def _element(
|
1344 | 1505 | html_element: str,
|
|
0 commit comments