Skip to content

Commit

Permalink
auto resize fmt, and min widths for column headers (#2299)
Browse files Browse the repository at this point in the history
  • Loading branch information
universalmind303 committed Jan 11, 2022
1 parent 9171649 commit ec1873c
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 61 deletions.
2 changes: 1 addition & 1 deletion nodejs-polars/__tests__/dataframe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1853,7 +1853,7 @@ describe("meta", () => {
│ --- │
│ f64 │
╞═════╡
│ 1
│ 1.0
└─────┘`;
const actualInspect = df[Symbol.for("nodejs.util.inspect.custom")]();
const dfString = df.toString();
Expand Down
8 changes: 4 additions & 4 deletions nodejs-polars/__tests__/series.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ describe("series", () => {
${"concat"} | ${pl.Series([1]).concat(pl.Series([2, 3]))} | ${pl.Series([1, 2, 3])}
${"cumMax"} | ${pl.Series([3, 2, 4]).cumMax()} | ${pl.Series([3, 3, 4])}
${"cumMin"} | ${pl.Series([3, 2, 4]).cumMin()} | ${pl.Series([3, 2, 2])}
${"cumProd"} | ${pl.Series("", [1, 2, 3], pl.Int32).cumProd()} | ${pl.Series("", [1, 2, 6], pl.Int32)}
${"cumProd"} | ${pl.Series("", [1, 2, 3], pl.Int32).cumProd()} | ${pl.Series("", [1n, 2n, 6n], pl.Int64)}
${"cumSum"} | ${pl.Series("", [1, 2, 3], pl.Int32).cumSum()} | ${pl.Series("", [1, 3, 6], pl.Int32)}
${"diff"} | ${pl.Series([1, 2, 12]).diff(1, "drop").toJS()} | ${pl.Series([1, 10]).toJS()}
${"diff"} | ${pl.Series([1, 11]).diff(1, "ignore")} | ${pl.Series("", [null, 10], pl.Float64, false)}
Expand Down Expand Up @@ -631,7 +631,7 @@ describe("series", () => {
${"toFrame"} | ${pl.Series("foo", [1, 2, 3]).toFrame().toJSON()} | ${pl.DataFrame([pl.Series("foo", [1, 2, 3])]).toJSON()}
${"shiftAndFill"} | ${pl.Series("foo", [1, 2, 3]).shiftAndFill(1, 99)} | ${pl.Series("foo", [99, 1, 2])}
`("$# $name: expected matches actual ", ({expected, actual}) => {
expect(expected).toStrictEqual(actual);
expect(actual).toStrictEqual(expected);
});
it("set: expected matches actual", () => {
const expected = pl.Series([99, 2, 3]);
Expand Down Expand Up @@ -783,10 +783,10 @@ describe("comparators & math", () => {
});
describe("series proxy & metadata", () => {
test("toString & inspect", () => {
const s = pl.Series("foo", [1, 2, 3]);
const s = pl.Series("foo", [1, 2, 3], pl.Int16);
const sString = s.toString();
const inspectString = s[Symbol.for("nodejs.util.inspect.custom")]();
const expected = "shape: (3,)\nSeries: 'foo' [f64]\n[\n\t1\n\t2\n\t3\n]";
const expected = "shape: (3,)\nSeries: 'foo' [i16]\n[\n\t1\n\t2\n\t3\n]";
expect(sString).toStrictEqual(expected);
expect(inspectString).toStrictEqual(expected);
});
Expand Down
2 changes: 1 addition & 1 deletion polars/polars-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ ahash = "0.7"
anyhow = "1.0"

base64 = { version = "0.13", optional = true }
comfy-table = { version = "4.0", optional = true }
comfy-table = { version = "5.0", optional = true }
hashbrown = { version = "0.11", features = ["rayon"] }
hex = { version = "0.4", optional = true }
jsonpath_lib = { version = "0.3.0", optional = true, git = "https://github.com/ritchie46/jsonpath", branch = "improve_compiled" }
Expand Down
118 changes: 81 additions & 37 deletions polars/polars-core/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,24 +302,24 @@ impl Debug for DataFrame {
Display::fmt(self, f)
}
}

#[cfg(any(feature = "plain_fmt", feature = "pretty_fmt"))]
fn prepare_row(row: Vec<Cow<'_, str>>, n_first: usize, n_last: usize) -> Vec<String> {
fn make_str_val(v: &str) -> String {
let string_limit = 32;
let v_trunc = &v[..v
.char_indices()
.take(string_limit)
.last()
.map(|(i, c)| i + c.len_utf8())
.unwrap_or(0)];
if v == v_trunc {
v.to_string()
} else {
format!("{}...", v_trunc)
}
fn make_str_val(v: &str) -> String {
let string_limit = 32;
let v_trunc = &v[..v
.char_indices()
.take(string_limit)
.last()
.map(|(i, c)| i + c.len_utf8())
.unwrap_or(0)];
if v == v_trunc {
v.to_string()
} else {
format!("{}...", v_trunc)
}
}

#[cfg(any(feature = "plain_fmt", feature = "pretty_fmt"))]
fn prepare_row(row: Vec<Cow<'_, str>>, n_first: usize, n_last: usize) -> Vec<String> {
let reduce_columns = n_first + n_last < row.len();
let mut row_str = Vec::with_capacity(n_first + n_last + reduce_columns as usize);
for v in row[0..n_first].iter() {
Expand All @@ -346,6 +346,7 @@ impl Display for DataFrame {
.unwrap_or_else(|_| "8".to_string())
.parse()
.unwrap_or(8);

#[cfg(any(feature = "plain_fmt", feature = "pretty_fmt"))]
let max_n_rows = {
let max_n_rows = std::env::var("POLARS_FMT_MAX_ROWS")
Expand All @@ -365,22 +366,52 @@ impl Display for DataFrame {
};
let reduce_columns = n_first + n_last < self.width();

let field_to_str = |f: &Field| format!("{}\n---\n{}", f.name(), f.data_type());

let mut names = Vec::with_capacity(n_first + n_last + reduce_columns as usize);
let schema = self.schema();
let fields = schema.fields();
for field in fields[0..n_first].iter() {
names.push(field_to_str(field))
}
if reduce_columns {
names.push("...".to_string())
}
for field in fields[self.width() - n_last..].iter() {
names.push(field_to_str(field))

#[cfg(not(feature = "pretty_fmt"))]
{
let field_to_str = |f: &Field| format!("{}\n---\n{}", f.name(), f.data_type());
let schema = self.schema();
let fields = schema.fields();
for field in fields[0..n_first].iter() {
names.push(field_to_str(field));
}
if reduce_columns {
names.push("...".into());
}
for field in fields[self.width() - n_last..].iter() {
names.push(field_to_str(field));
}
}

#[cfg(feature = "pretty_fmt")]
{
let field_to_str = |f: &Field| {
let name = make_str_val(f.name());
let lower_bounds = std::cmp::max(5, std::cmp::min(12, name.len()));
let s = format!("{}\n---\n{}", name, f.data_type());
(s, lower_bounds)
};
let tbl_lower_bounds = |l: usize| {
comfy_table::ColumnConstraint::LowerBoundary(comfy_table::Width::Fixed(l as u16))
};
let mut constraints = Vec::with_capacity(n_first + n_last + reduce_columns as usize);
let schema = self.schema();
let fields = schema.fields();
for field in fields[0..n_first].iter() {
let (s, l) = field_to_str(field);
names.push(s);
constraints.push(tbl_lower_bounds(l));
}
if reduce_columns {
names.push("...".into());
constraints.push(tbl_lower_bounds(5));
}
for field in fields[self.width() - n_last..].iter() {
let (s, l) = field_to_str(field);
names.push(s);
constraints.push(tbl_lower_bounds(l));
}
let mut table = Table::new();
let preset = if std::env::var("POLARS_FMT_NO_UTF8").is_ok() {
ASCII_FULL
Expand All @@ -390,16 +421,8 @@ impl Display for DataFrame {

table
.load_preset(preset)
.set_content_arrangement(ContentArrangement::Dynamic)
.set_table_width(
std::env::var("POLARS_TABLE_WIDTH")
.map(|s| {
s.parse::<u16>()
.expect("could not parse table width argument")
})
.unwrap_or(100),
)
.set_header(names);
.set_content_arrangement(ContentArrangement::Dynamic);

let mut rows = Vec::with_capacity(max_n_rows);
if self.height() > max_n_rows {
for i in 0..(max_n_rows / 2) {
Expand All @@ -426,6 +449,27 @@ impl Display for DataFrame {
}
}

table.set_header(names).set_constraints(constraints);

let tbl_width = std::env::var("POLARS_TABLE_WIDTH")
.map(|s| {
Some(
s.parse::<u16>()
.expect("could not parse table width argument"),
)
})
.unwrap_or(None);
// if tbl_width is explicitly set, use it
if let Some(w) = tbl_width {
table.set_table_width(w);
}

// if no tbl_width (its not-tty && it is not explicitly set), then set default
// this is needed to support non-tty applications
if !table.is_tty() && table.get_table_width().is_none() {
table.set_table_width(100);
}

write!(f, "shape: {:?}\n{}", self.shape(), table)?;
}
#[cfg(not(any(feature = "plain_fmt", feature = "pretty_fmt")))]
Expand Down
20 changes: 10 additions & 10 deletions py-polars/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 17 additions & 8 deletions py-polars/tests/test_cfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,25 @@ def test_tables(environ: None) -> None:


def test_tbl_width_chars(environ: None) -> None:
df = pl.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})
pl.Config.set_tbl_width_chars(100)
assert max(len(line) for line in str(df).split("\n")) == 19
df = pl.DataFrame(
{
"a really long col": [1, 2, 3],
"b": ["", "this is a string value that will be truncated", None],
"this is 10": [4, 5, 6],
}
)

assert max(len(line) for line in str(df).split("\n")) == 72

pl.Config.set_tbl_width_chars(60)
assert max(len(line) for line in str(df).split("\n")) == 60

pl.Config.set_tbl_width_chars(15)
assert max(len(line) for line in str(df).split("\n")) == 15
# formula for determining min width is
# sum(max(min(header.len, 12), 5)) + header.len + 1
# so we end up with 12+5+10+4 = 31

# this will not squeezed below 13 characters, so 10 yields 13
pl.Config.set_tbl_width_chars(10)
assert max(len(line) for line in str(df).split("\n")) == 13
pl.Config.set_tbl_width_chars(0)
assert max(len(line) for line in str(df).split("\n")) == 31


def test_set_tbl_cols(environ: None) -> None:
Expand Down

0 comments on commit ec1873c

Please sign in to comment.