Skip to content

Commit

Permalink
feat(rust,python): option to right-align numeric columns
Browse files Browse the repository at this point in the history
  • Loading branch information
alicja-januszkiewicz authored and alexander-beedie committed Aug 27, 2023
1 parent 6e59512 commit ab62e20
Show file tree
Hide file tree
Showing 5 changed files with 847 additions and 13 deletions.
1 change: 1 addition & 0 deletions crates/polars-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub(crate) const FMT_MAX_COLS: &str = "POLARS_FMT_MAX_COLS";
pub(crate) const FMT_MAX_ROWS: &str = "POLARS_FMT_MAX_ROWS";
pub(crate) const FMT_STR_LEN: &str = "POLARS_FMT_STR_LEN";
pub(crate) const FMT_TABLE_CELL_ALIGNMENT: &str = "POLARS_FMT_TABLE_CELL_ALIGNMENT";
pub(crate) const FMT_TABLE_CELL_NUMERIC_ALIGNMENT: &str = "POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT";
pub(crate) const FMT_TABLE_DATAFRAME_SHAPE_BELOW: &str = "POLARS_FMT_TABLE_DATAFRAME_SHAPE_BELOW";
pub(crate) const FMT_TABLE_FORMATTING: &str = "POLARS_FMT_TABLE_FORMATTING";
pub(crate) const FMT_TABLE_HIDE_COLUMN_DATA_TYPES: &str = "POLARS_FMT_TABLE_HIDE_COLUMN_DATA_TYPES";
Expand Down
45 changes: 34 additions & 11 deletions crates/polars-core/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub enum FloatFmt {
Full,
}
static FLOAT_FMT: AtomicU8 = AtomicU8::new(FloatFmt::Mixed as u8);
static FLOAT_PRECISION: AtomicU8 = AtomicU8::new(u8::MAX);

pub fn get_float_fmt() -> FloatFmt {
match FLOAT_FMT.load(Ordering::Relaxed) {
Expand All @@ -42,10 +43,18 @@ pub fn get_float_fmt() -> FloatFmt {
}
}

pub fn get_float_precision() -> u8 {
FLOAT_PRECISION.load(Ordering::Relaxed)
}

pub fn set_float_fmt(fmt: FloatFmt) {
FLOAT_FMT.store(fmt as u8, Ordering::Relaxed)
}

pub fn set_float_precision(precision: u8) {
FLOAT_PRECISION.store(precision, Ordering::Relaxed)
}

macro_rules! format_array {
($f:ident, $a:expr, $dtype:expr, $name:expr, $array_type:expr) => {{
write!(
Expand Down Expand Up @@ -656,19 +665,24 @@ impl Display for DataFrame {
}

// set alignment of cells, if defined
if std::env::var(FMT_TABLE_CELL_ALIGNMENT).is_ok() {
// for (column_index, column) in table.column_iter_mut().enumerate() {
if std::env::var(FMT_TABLE_CELL_ALIGNMENT).is_ok()
| std::env::var(FMT_TABLE_CELL_NUMERIC_ALIGNMENT).is_ok()
{
let str_preset = std::env::var(FMT_TABLE_CELL_ALIGNMENT)
.unwrap_or_else(|_| "DEFAULT".to_string());
for column in table.column_iter_mut() {
if str_preset == "RIGHT" {
column.set_cell_alignment(CellAlignment::Right);
} else if str_preset == "LEFT" {
column.set_cell_alignment(CellAlignment::Left);
} else if str_preset == "CENTER" {
column.set_cell_alignment(CellAlignment::Center);
} else {
column.set_cell_alignment(CellAlignment::Left);
let num_preset = std::env::var(FMT_TABLE_CELL_NUMERIC_ALIGNMENT)
.unwrap_or_else(|_| str_preset.to_string());
for (column_index, column) in table.column_iter_mut().enumerate() {
let dtype = fields[column_index].data_type();
let mut preset = str_preset.as_str();
if dtype.to_physical().is_numeric() {
preset = num_preset.as_str();
}
match preset {
"RIGHT" => column.set_cell_alignment(CellAlignment::Right),
"LEFT" => column.set_cell_alignment(CellAlignment::Left),
"CENTER" => column.set_cell_alignment(CellAlignment::Center),
_ => {}
}
}
}
Expand Down Expand Up @@ -710,6 +724,15 @@ const SCIENTIFIC_BOUND: f64 = 999999.0;

fn fmt_float<T: Num + NumCast>(f: &mut Formatter<'_>, width: usize, v: T) -> fmt::Result {
let v: f64 = NumCast::from(v).unwrap();

let precision = get_float_precision();
if precision != u8::MAX {
if format!("{v:.precision$}", precision = precision as usize).len() > 19 {
return write!(f, "{v:>width$.precision$e}", precision = precision as usize);
}
return write!(f, "{v:>width$.precision$}", precision = precision as usize);
}

if matches!(get_float_fmt(), FloatFmt::Full) {
return write!(f, "{v:>width$}");
}
Expand Down
70 changes: 69 additions & 1 deletion py-polars/polars/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ def _get_float_fmt() -> str: # pragma: no cover
return "n/a"


def _get_float_precision() -> str:
return "n/a"


# note: module not available when building docs
with contextlib.suppress(ImportError):
from polars.polars import get_float_fmt as _get_float_fmt # type: ignore[no-redef]
from polars.polars import ( # type: ignore[no-redef]
get_float_precision as _get_float_precision,
)
from polars.polars import set_float_fmt as _set_float_fmt
from polars.polars import set_float_precision as _set_float_precision

if TYPE_CHECKING:
from types import TracebackType
Expand All @@ -35,7 +43,9 @@ def _get_float_fmt() -> str: # pragma: no cover
"POLARS_FMT_MAX_COLS",
"POLARS_FMT_MAX_ROWS",
"POLARS_FMT_STR_LEN",
"POLARS_FMT_NUM_LEN",
"POLARS_FMT_TABLE_CELL_ALIGNMENT",
"POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT",
"POLARS_FMT_TABLE_DATAFRAME_SHAPE_BELOW",
"POLARS_FMT_TABLE_FORMATTING",
"POLARS_FMT_TABLE_HIDE_COLUMN_DATA_TYPES",
Expand All @@ -51,7 +61,10 @@ def _get_float_fmt() -> str: # pragma: no cover

# vars that set the rust env directly should declare themselves here as the Config
# method name paired with a callable that returns the current state of that value:
_POLARS_CFG_DIRECT_VARS = {"set_fmt_float": _get_float_fmt}
_POLARS_CFG_DIRECT_VARS = {
"set_fmt_float": _get_float_fmt,
"set_float_precision": _get_float_precision,
}


class Config(contextlib.ContextDecorator):
Expand Down Expand Up @@ -186,6 +199,7 @@ def restore_defaults(cls) -> type[Config]:

# apply any 'direct' setting values
cls.set_fmt_float()
cls.set_float_precision()
return cls

@classmethod
Expand Down Expand Up @@ -441,6 +455,46 @@ def set_tbl_cell_alignment(
os.environ["POLARS_FMT_TABLE_CELL_ALIGNMENT"] = format
return cls

@classmethod
def set_tbl_cell_numeric_alignment(
cls, format: Literal["LEFT", "CENTER", "RIGHT"]
) -> type[Config]:
"""
Set table cell alignment for numeric columns.
Parameters
----------
format : str
* "LEFT": left aligned
* "CENTER": center aligned
* "RIGHT": right aligned
Examples
--------
>>> df = pl.DataFrame(
... {"column_abc": [11, 2, 333], "column_xyz": [True, False, True]}
... )
>>> pl.Config.set_tbl_cell_numeric_alignment("RIGHT") # doctest: +SKIP
# ...
# shape: (3, 2)
# ┌────────────┬────────────┐
# │ column_abc ┆ column_xyz │
# │ --- ┆ --- │
# │ i64 ┆ bool │
# ╞════════════╪════════════╡
# │ 11 ┆ true │
# │ 2 ┆ false │
# │ 333 ┆ true │
# └────────────┴────────────┘
Raises
------
KeyError: if alignment string not recognised.
"""
os.environ["POLARS_FMT_TABLE_CELL_NUMERIC_ALIGNMENT"] = format
return cls

@classmethod
def set_tbl_cols(cls, n: int) -> type[Config]:
"""
Expand Down Expand Up @@ -775,3 +829,17 @@ def set_verbose(cls, active: bool = True) -> type[Config]:
"""Enable additional verbose/debug logging."""
os.environ["POLARS_VERBOSE"] = str(int(active))
return cls

@classmethod
def set_float_precision(cls, precision: str = "255") -> type[Config]:
"""
Control how floating point values are displayed.
Parameters
----------
precision : int
Number of decimal places to display
"""
_set_float_precision(precision)
return cls
Loading

0 comments on commit ab62e20

Please sign in to comment.