Repository: aeturrell/skimpy

Issue:
Column name colour - how can we change / customise? 
Hi there, great package here, wondering if there is an easy way to change the colour used for the column names in the output - current default uses pink, unfortunately I have a grey terminal background, pink foreground font is pretty much impossible to see... 

Thanks


Discussion:

Resolving patch:
diff --git a/src/skimpy/__init__.py b/src/skimpy/__init__.py
--- a/src/skimpy/__init__.py
+++ b/src/skimpy/__init__.py
@@ -6,6 +6,7 @@
 import rich
 from numpy.random import Generator
 from numpy.random import PCG64
+from rich.columns import Columns
 from rich.console import Console
 from rich.panel import Panel
 from rich.table import Table
@@ -32,9 +33,12 @@
 def dataframe_to_rich_table(
     table_name: str,
     df: pd.DataFrame,
-    row_limit: int = 20,
-    col_limit: int = 10,
-    str_limit: int = 20,
+    number: str = "cyan",
+    category: str = "magenta",
+    datetime: str = "red",
+    string: str = "green",
+    bool: str = "turquoise2",
+    object: str = "medium_purple1",
 ) -> rich.table.Table:
     """Converts a dataframe into a rich table.
 
@@ -43,26 +47,33 @@ def dataframe_to_rich_table(
     conversion and also colours entries in tables depending on their broad
     datatype. This function processes summaries (themselves dataframes) and is
     used to produce a table for each data type in the dataframe to be skimmed.
+    A list of standard colours may be found at
+    https://rich.readthedocs.io/en/stable/appendix/colors.html#appendix-colors
 
     Args:
         table_name (str): Usually one of 'number', 'bool', etc. Used as title.
         df (pd.DataFrame): summary of all data of one datatype
-        row_limit (int): For longer frames, limit no rows shown. Default is 20.
-        col_limit (int): For longer frames, limit cols shown. Default is 10.
-        str_limit (int): For longer strings, limit chars shown. Default is 20.
+        number (str): colour to render numbers in
+        category (str): colour to render categories in
+        datetime (str): colour to render datetimes in
+        string (str): colour to render strings in
+        bool (str): colour to render bools in
+        object (str): colour to render objects in
 
     Returns:
-        rich.table.Table: [description]
+        rich.table.Table: instance of Table from the rich package
     """
+    str_limit: int = 20  # For longer strings, limit chars shown.
     df = df.reset_index().rename(columns={"index": ""})
     table = Table(show_footer=False, expand=True, title=table_name, show_header=True)
+    # generate dict of types to colours
     datatype_colours = {
-        "number": "cyan",
-        "category": "magenta",
-        "datetime": "red",
-        "string": "green",
-        "bool": "turquoise2",
-        "object": "medium_purple1",
+        "number": number,
+        "category": category,
+        "datetime": datetime,
+        "string": string,
+        "bool": bool,
+        "object": object,
     }
     cols_to_cat_map = dict(
         zip(
@@ -81,15 +92,14 @@ def dataframe_to_rich_table(
     type_to_colour = dict(
         zip(type_by_col.keys(), [datatype_colours[x] for x in type_by_col.values()])
     )
-    columns = list(df.columns[:col_limit])
+    columns = list(df.columns)
     col_pos_to_colour = dict(
         zip(range(len(columns)), [type_to_colour[x] for x in columns])
     )
-    rows = df.values[:row_limit]
+    rows = df.values
     for col in columns:
         table.add_column(str(col), overflow="fold")
     for row in rows:
-        row = row[:col_limit]
         row = [
             Text(str(item)[:str_limit], style=col_pos_to_colour[i])
             for i, item in enumerate(row)
@@ -296,7 +306,9 @@ def datetime_variable_summary_table(xf: pd.DataFrame) -> pd.DataFrame:
 
 
 @typechecked
-def skim(df: pd.DataFrame) -> None:
+def skim(
+    df: pd.DataFrame, header_style: str = "bold cyan", **colour_kwargs: str
+) -> None:
     """Skim a data frame and return statistics.
 
     skim is an alternative to pandas.DataFrame.summary(), quickly providing
@@ -304,9 +316,12 @@ def skim(df: pd.DataFrame) -> None:
     functions based on the types of columns in the dataframe. You may get
     better results from ensuring that you set the datatypes in your dataframe
     you want before running skim.
+    The colour_kwargs (str) are defined in dataframe_to_rich_table.
 
     Args:
         df (pd.DataFrame): Dataframe to skim
+        header_style (str): A style to use for headers. See Rich API Styles.
+        colour_kwargs (dict[str]): colour keyword arguments for rich table
     """
     if hasattr(df, "name") and "name" not in df.columns:
         name = df.name
@@ -316,7 +331,7 @@ def skim(df: pd.DataFrame) -> None:
     # Data summary
     tab_1_data = {"Number of rows": df.shape[0], "Number of columns": df.shape[1]}
     dat_sum_table = Table(
-        title="Data Summary", show_header=True, header_style="bold cyan"
+        title="Data Summary", show_header=True, header_style=header_style
     )
     dat_sum_table.add_column(name)
     dat_sum_table.add_column("Values")
@@ -324,7 +339,7 @@ def skim(df: pd.DataFrame) -> None:
         dat_sum_table.add_row(key, str(val))
     # Data tpes
     types_sum_table = Table(
-        title="Data Types", show_header=True, header_style="bold cyan"
+        title="Data Types", show_header=True, header_style=header_style
     )
     tab_2_data = df.dtypes.astype(str).value_counts().to_dict()
     types_sum_table.add_column("Column Type")
@@ -334,8 +349,12 @@ def skim(df: pd.DataFrame) -> None:
     # Categorys
     if "category" in df.dtypes.astype(str).to_list():
         xf = pd.DataFrame(df.dtypes.astype(str))
-        cat_sum_table = Table()
-        cat_sum_table.add_column("[bold cyan]Categorical Variables[/bold cyan]")
+        cat_sum_table = Table(
+            title="Categories", show_header=True, header_style=header_style
+        )
+        header_string = f"[{header_style}]Categorical Variables[/{header_style}]"
+        print(header_string)
+        cat_sum_table.add_column(header_string)
         cat_names = list(xf[xf[0] == "category"].index)
         for cat in cat_names:
             cat_sum_table.add_row(cat)
@@ -352,13 +371,16 @@ def skim(df: pd.DataFrame) -> None:
         xf = df.select_dtypes(col_type)
         if not xf.empty:
             sum_df = summary_func(xf)
-            list_of_tabs.append(dataframe_to_rich_table(col_type, sum_df.round(2)))
+            list_of_tabs.append(
+                dataframe_to_rich_table(col_type, sum_df.round(2), **colour_kwargs)
+            )
+    # Put all of the info together
     grid = Table.grid(expand=True)
-    grid.add_column(justify="left")
-    grid.add_row(dat_sum_table)
-    grid.add_row(types_sum_table)
+    tables_list = [dat_sum_table, types_sum_table]
     if "category" in df.dtypes.astype(str).to_list():
-        grid.add_row(cat_sum_table)
+        tables_list.append(cat_sum_table)
+    grid.add_row(Columns(tables_list))
+    grid.add_column(justify="left")
     for sum_tab in list_of_tabs:
         grid.add_row(sum_tab)
     console.print(Panel(grid, title="skimpy summary", subtitle="End"))


Test patch:
diff --git a/src/skimpy/test_panel.py b/src/skimpy/test_panel.py
new file mode 100644
--- /dev/null
+++ b/src/skimpy/test_panel.py
@@ -0,0 +1,5 @@
+from skimpy import skim, generate_test_data
+
+df = generate_test_data()
+skim(df, datetime="chartreuse1")
+skim(df, header_style="bold green")
\ No newline at end of file
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -26,3 +26,9 @@ def test_000_basic_functionality() -> None:
     """Tests that a skim of the test data works."""
     df = generate_test_data()
     skim(df)
+
+
+def test_001_colour_kwargs() -> None:
+    """Tests that colour keyword arguments work."""
+    df = generate_test_data()
+    skim(df, datetime="chartreuse1")