Explore, Select & Filter - Python
Quote
“If we have data, let’s look at data. If all we have are opinions, let’s go with mine.”
— Jim Barksdale
Summary
Uses real EuroStoxx 50 OHLCV, dimension, and scoring tables to show how Pandas and Polars preview data, inspect structure and completeness, select rows and columns, and build reproducible filter logic for exploratory analysis and profiling.
Head / Tail / Sample / Glimpse
- Preview row shape and representative values with
head(),tail(),sample(), and Polarsglimpse(), including seeded sampling for reproducible notebook output- Establish the three working datasets up front:
eurostoxx50_ohlcv,index_dim, andscores_dailyShape / Describe / Info
- Inspect row/column counts, summary statistics, dtypes, non-null counts, memory usage, schema, and transposed column previews with Pandas
.info()/.describe()and Polarsdf.schema,null_count(), andestimated_size()- Contrast Pandas’ numeric-default
describe()with broader Polars summaries and Pandas workarounds such asinclude="all"Value Counts / Unique / N-Unique / Null Inspection
- Profile categorical frequency, cardinality, duplicate keys, and missingness with
value_counts(),unique(),n_unique(),.isna()/.is_null(), and null-count summaries- Show how null behavior differs across libraries, including Pandas
dropna=Falserequirements and NaN-vs-null pitfallsScores / Dimension / Profiling Strategies
- Profile the
scores_dailytable for sparse metrics, ranked score columns, and null-heavy analytical fields before downstream filtering- Validate the
index_dimdimension table for join readiness, categorical consistency, and key uniqueness, then generalize the profiling workflow into a repeatable checklistSelecting Rows & Columns
- Select by label, position, name, dtype, and pattern with
.loc,.iloc, bracket indexing,select(),select_dtypes(), and Polars selectors viapolars.selectors as cs- Contrast Pandas’ index-aware accessors with Polars’ index-free model, including slice semantics, row/column subsets, and how to emulate label filters in Polars
Filtering Rows
- Filter with boolean masks,
df.filter(pl.col(...)),.query(),isin()/is_in(),between()/is_between(), string/date predicates, null checks, seeded sampling, deduplication, null dropping, sorting, and semi-join-style membership filters- Compare conditional replacement patterns: Pandas
where()/mask()versus Polarswhen().then().otherwise()Operations and safety
- Warnings: Pandas
filter()selects columns not rows, boolean logic requires&/|/~plus parentheses, NaN equality fails,.loc/.ilocslice semantics differ, deduplication depends on row order, and unseededsample()is non-reproducible- Recommendations: 7 practices covering profile-first workflow, early filter/select pushdown,
csselectors,validate=before join-dependent filters,is_in()over chained ORs,dropna=Falsein Pandasvalue_counts(), and sorting beforehead()/tail()- Troubleshooting: 10 failure modes covering missing columns, unexpectedly empty filters, boolean-precedence
TypeErrors, Series-vs-DataFrame selection mistakes, unordered uniques, and dtype mismatches inisin()
Glossary
head()/tail()
Methods that return the first or last
nrows of a DataFrame, usually used as the fastest preview of raw tabular values.They are the first inspection step in this note because they reveal row layout, obvious type issues, and whether the loaded data even resembles the expected dataset.
Preview is not profiling
A few visible rows can confirm column names and rough shape, but they do not reveal null rates, duplicate keys, or hidden outliers. Use them as the opening check, not the full diagnosis.
sample()
A method that returns a random subset of rows from a DataFrame instead of the first or last rows in storage order.
It matters here because random sampling helps catch localized anomalies that
head()andtail()can miss, especially in time-ordered datasets.Seed controls reproducibility
Without
random_state=in Pandas orseed=in Polars, the sampled rows change every run. That breaks notebook reproducibility and makes debugging harder.
shape
The
(rows, columns)dimensions of a DataFrame.It is the fastest structural sanity check in the note because many downstream issues start with the wrong row count, unexpected width, or accidental empty results.
Cheap but high-signal
A shape mismatch often reveals upstream filter, join, or load errors before you inspect any actual cell values.
describe()
A summary method that computes descriptive statistics such as count, mean, standard deviation, min, max, and percentiles for selected columns.
It is used here as the first numeric quality screen for distributions, missingness, impossible ranges, and suspiciously constant columns.
Pandas hides non-numeric columns
Pandas excludes many non-numeric fields by default. If string, categorical, or datetime columns matter, use
include="all"or inspect them separately.
info()
A Pandas-only structural summary that reports column names, non-null counts, dtypes, and memory usage.
It matters because it compresses schema validation and completeness checks into one glance before deeper selection or filtering work.
No Polars equivalent
Polars splits the same insight across
schema,null_count(), andestimated_size(). You need multiple calls rather than one.info()method.
glimpse()
A Polars display method that summarizes columns vertically, showing each column’s name, dtype, and preview values in a transposed layout.
It is useful in this note for wide DataFrames where a normal horizontal preview hides important columns off-screen.
Display helper, not transformation
glimpse()is for inspection only. It does not change the underlying DataFrame or replace explicit schema and null checks.
value_counts()
A frequency-count operation that reports how often each unique value occurs in a column.
It is central to the note’s profiling workflow because it surfaces dominant categories, unexpected labels, and join-key quality issues quickly.
Pandas drops NaN by default
In Pandas, null-like values disappear unless you pass
dropna=False. If you forget that flag, your cardinality analysis underreports missing data.
unique()/n_unique()
Distinct-value operations:
unique()returns the distinct values themselves, whilen_unique()returns only the count.They matter for deciding whether a column is categorical, suitable as a key, or contaminated by unexpected duplicates.
Uniqueness is often unordered
Distinct values are commonly returned in arbitrary order. If the order matters for display or testing, sort the result explicitly.
Null / missing value
The absence of a value in a column, represented as Arrow nulls in Polars and as
NaNor nullable sentinels such aspd.NAin Pandas.Missingness is a core concern in this note because it changes counts, filters, deduplication, joins, and statistical summaries.
Equality does not find nulls
Missing values are not reliably detected with
==. Use.isna(),.notna(),.is_null(), or.is_not_null()instead.
Boolean mask
A per-row sequence of
TrueandFalsevalues used to keep rows whose condition evaluates toTrue.It is the basic row-filtering mechanism across the note, whether expressed directly in Pandas or through Polars expressions.
Use bitwise operators
Combine conditions with
&,|, and~, not Pythonand,or, ornot. Each condition also needs parentheses to avoid precedence bugs.
.loc/.iloc
Pandas accessors for label-based selection (
.loc) and integer-position-based selection (.iloc).They matter because the note compares Pandas’ index-aware selection model with Polars’ index-free row/column access patterns.
Slice endpoints differ
.loclabel slices include the right endpoint, while.ilocpositional slices exclude it. Mixing the two models causes off-by-one selection errors.
Filter expression
A boolean expression passed to Polars
filter()and built from column expressions such aspl.col("close") > 50.It matters because Polars filtering is expression-driven and composes cleanly with selection, lazy execution, and optimizer pushdown.
Pandas
filter()means something elseIn Polars,
filter()selects rows. In Pandas,filter()is mostly for column-label selection. The same method name points at different operations.
query()
A Pandas method that filters rows using a string expression instead of explicit bracketed boolean masks.
It appears in the note as an alternative filtering syntax for readable notebook code when conditions are simple and column names are expression-friendly.
String syntax has limits
query()is convenient, but it is still parsed text. Complex expressions, odd column names, or heavy refactoring can make explicit masks safer and clearer.
Selector /
cs
The Polars selectors API, imported as
polars.selectors as cs, for choosing columns by dtype family, pattern, or set logic.It matters because the note uses selectors to avoid brittle hard-coded column lists when exploring changing schemas.
Separate module import
Selectors are not available automatically from the main
plnamespace in the same way users often expect. Importpolars.selectors as csexplicitly.
isin()/is_in()
Membership tests that return a boolean mask indicating whether each value belongs to a provided list or set of candidates.
They are used in the note for watchlist-style filters, semi-join-style membership checks, and compact alternatives to chained equality conditions.
Method names differ
Pandas uses
.isin(), while Polars uses.is_in(). The underscore is easy to miss and is a common source of copy-paste errors.
between()/is_between()
Range predicates that test whether values fall between two bounds.
They matter because price ranges, date windows, and score thresholds are common filter shapes throughout exploratory work.
Boundary rules vary
Pandas is inclusive by default, while Polars exposes boundary control through
closed=. If edge values matter, make the inclusion rule explicit.
where()/mask()/when().then().otherwise()
Conditional replacement patterns that keep or replace values based on a boolean condition, with Pandas and Polars exposing different APIs.
They matter because the note uses them to express row-dependent value logic without manually splitting and recombining DataFrames.
Pandas
whereandmaskinvert each other
where()keeps values where the condition isTrue, whilemask()replaces values where the condition isTrue. Polars’when().then().otherwise()is more explicit about branch direction.
drop_duplicates()/unique()
Row-deduplication operations that keep one representative row from repeated values or repeated key combinations.
They matter because profiling and filtering often depend on confirming whether keys are truly unique before joins or aggregations.
Kept row depends on order
If you keep the “first” duplicate without sorting deterministically, the survivor is only as stable as the incoming row order.
dropna()/drop_nulls()
Row-removal operations that discard rows containing missing values, optionally limited to a subset of columns.
They matter because many filters and comparisons behave differently once null-bearing rows are removed or isolated.
Row loss can be silent
Dropping nulls can remove far more data than intended, especially in wide tables. Always specify the critical subset when only certain columns matter.
Semi-join filter
A filter that keeps rows from one table only when their key exists in another table, without importing the other table’s columns.
It matters because the note contrasts Pandas membership-style workarounds with Polars’ native
how="semi"join for table-driven filtering.Useful for key membership
Semi-joins are clearer than full joins when you only care whether a key exists, not about bringing reference columns into the result.
Sort /
sort_values()
Row-ordering operations that arrange a DataFrame by one or more columns in ascending or descending order.
They matter because many previews and top-N selections in the note only make sense after the data is explicitly ordered.
Parameter names differ
Pandas uses
ascending=, while Polars usesdescending=. The intent is the same, but the parameter names invert the phrasing.
import pandas as pd
import polars as pl
import polars.selectors as cs
import numpy as np
from pathlib import Path
import io
from IPython.display import display, Markdown
html_formatter = get_ipython().display_formatter.formatters['text/html'] # type: ignore
html_formatter.for_type(pd.DataFrame, lambda df: df.to_html())
html_formatter.for_type(pd.Series, lambda s: s.to_frame().to_html())
pl.Config.set_tbl_rows(100)
pd.set_option("display.max_rows", 100)
DATA = Path("../data")
# Core datasets
ohlcv_pd = pd.read_parquet(DATA / "eurostoxx50_ohlcv.parquet") # 66K rows, daily OHLCV
ohlcv_pl = pl.read_parquet(DATA / "eurostoxx50_ohlcv.parquet")
dim_pd = pd.read_parquet(DATA / "index_dim.parquet") # 169 rows, stock metadata
dim_pl = pl.read_parquet(DATA / "index_dim.parquet")
scores_pd = pd.read_parquet(DATA / "scores_daily.parquet") # 466 rows, composite scores
scores_pl = pl.read_parquet(DATA / "scores_daily.parquet")
print(f"OHLCV: {ohlcv_pd.shape}, Dim: {dim_pd.shape}, Scores: {scores_pd.shape}")OHLCV: (66355, 12), Dim: (169, 26), Scores: (466, 36)Three datasets are used throughout: eurostoxx50_ohlcv (66K rows, daily OHLCV prices), index_dim (169 rows, stock metadata), and scores_daily (466 rows, composite scores with real nulls).
Head / Tail / Sample / Glimpse
The first step in data exploration is previewing rows. Both libraries provide .head() and .tail(). Polars additionally offers .glimpse() for a transposed column-by-column preview of data types and sample values.
Pandas | head() and tail()
display(Markdown("**First 5 rows (head):**"))
display(ohlcv_pd.head())First 5 rows (head)
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| 3 | 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 | False |
| 4 | 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | False |
display(Markdown("**Last 5 rows (tail):**"))
display(ohlcv_pd.tail())Last 5 rows (tail)
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 66350 | 64828 | WKL.AS | 2026-03-06 | 69.02 | 69.36 | 67.82 | 68.52 | 68.52 | 1143729 | 0.0 | 0.0 | False |
| 66351 | 66875 | WKL.AS | 2026-03-09 | 68.78 | 69.16 | 67.64 | 68.64 | 68.64 | 841503 | 0.0 | 0.0 | False |
| 66352 | 66876 | WKL.AS | 2026-03-10 | 68.80 | 69.16 | 66.34 | 67.16 | 67.16 | 1355645 | 0.0 | 0.0 | False |
| 66353 | 66877 | WKL.AS | 2026-03-11 | 67.50 | 69.60 | 67.02 | 67.22 | 67.22 | 1142531 | 0.0 | 0.0 | False |
| 66354 | 66929 | WKL.AS | 2026-03-12 | 67.00 | 67.54 | 66.28 | 67.32 | 67.32 | 210379 | 0.0 | 0.0 | False |
Pandas | sample()
display(Markdown("**Random sample of 5 rows:**"))
display(ohlcv_pd.sample(5, random_state=42))Random sample of 5 rows
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 43053 | 38920 | MUV2.DE | 2023-03-29 | 320.0000 | 322.600 | 318.400 | 322.4000 | 290.3200 | 195809 | 0.0 | 0.0 | False |
| 56209 | 5772 | SAP.DE | 2022-11-03 | 95.9200 | 96.340 | 95.080 | 95.5100 | 91.9052 | 1424973 | 0.0 | 0.0 | False |
| 53515 | 11015 | SAN.MC | 2022-09-15 | 2.5975 | 2.686 | 2.597 | 2.6765 | 2.3373 | 70158349 | 0.0 | 0.0 | False |
| 6498 | 28954 | AI.PA | 2025-08-12 | 173.3200 | 174.440 | 172.800 | 173.6800 | 173.6800 | 415652 | 0.0 | 0.0 | False |
| 63527 | 24956 | UCG.MI | 2025-07-07 | 56.4600 | 57.350 | 56.440 | 57.3500 | 56.0468 | 4705567 | 0.0 | 0.0 | False |
Polars | head() and tail()
display(Markdown("**First 5 rows (head):**"))
display(ohlcv_pl.head())First 5 rows (head)
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 | false |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | false |
display(Markdown("**Last 5 rows (tail):**"))
display(ohlcv_pl.tail())Last 5 rows (tail)
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 64828 | WKL.AS | 2026-03-06 | 69.02 | 69.36 | 67.82 | 68.52 | 68.52 | 1143729 | 0.0 | 0.0 | false |
| 66875 | WKL.AS | 2026-03-09 | 68.78 | 69.16 | 67.64 | 68.64 | 68.64 | 841503 | 0.0 | 0.0 | false |
| 66876 | WKL.AS | 2026-03-10 | 68.8 | 69.16 | 66.34 | 67.16 | 67.16 | 1355645 | 0.0 | 0.0 | false |
| 66877 | WKL.AS | 2026-03-11 | 67.5 | 69.6 | 67.02 | 67.22 | 67.22 | 1142531 | 0.0 | 0.0 | false |
| 66929 | WKL.AS | 2026-03-12 | 67.0 | 67.54 | 66.28 | 67.32 | 67.32 | 210379 | 0.0 | 0.0 | false |
Polars | sample() and glimpse()
display(Markdown("**Random sample of 5 rows:**"))
display(ohlcv_pl.sample(5, seed=42))Random sample of 5 rows
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 11529 | SAN.MC | 2024-09-18 | 4.511 | 4.5455 | 4.5065 | 4.5085 | 4.2785 | 16487238 | 0.0 | 0.0 | false |
| 35604 | CS.PA | 2025-10-14 | 39.34 | 40.27 | 39.25 | 40.18 | 40.18 | 3511125 | 0.0 | 0.0 | false |
| 63666 | WKL.AS | 2022-01-05 | 102.2 | 102.65 | 101.25 | 101.8 | 95.254 | 230509 | 0.0 | 0.0 | false |
| 33136 | PRX.AS | 2021-05-03 | 41.3654 | 41.737 | 41.0718 | 41.3746 | 40.8581 | 2177525 | 0.0 | 0.0 | false |
| 19417 | SAF.PA | 2024-07-12 | 204.2 | 204.8 | 201.3 | 204.8 | 202.5157 | 496739 | 0.0 | 0.0 | false |
display(Markdown("**Glimpse (transposed summary):**"))
# Transpose the glimpse into a horizontal table
cols = []
for line in ohlcv_pl.glimpse(return_type="string").strip().split("\n"):
parts = line.split()
if len(parts) >= 3:
name = parts[0]
dtype = parts[1]
preview = " ".join(parts[2:])
cols.append({"Column": name, "Type": dtype, "Preview": preview})
display(ohlcv_pl.head())Glimpse (transposed summary)
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 | false |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | false |
Shape / Describe / Info
Shape, summary statistics, column types, and memory usage are the core metadata inspection operations. Pandas provides .describe(), .info(), and .dtypes. Polars provides .describe(), .schema, and .dtypes.
Pandas | shape
for name, df in [("ohlcv", ohlcv_pd), ("dim", dim_pd), ("scores", scores_pd)]:
print(f"{name:>10s}: {df.shape[0]:>8,} rows x {df.shape[1]:>3} cols")ohlcv: 66,355 rows x 12 cols dim: 169 rows x 26 cols scores: 466 rows x 36 cols
Pandas | describe()
- Describe: Summary statistics: count, mean, std, min, max, quartiles.
display(Markdown("**Numeric summary:**"))
display(ohlcv_pd.describe())Numeric summary
| id | open | high | low | close | adj_close | volume | dividends | stock_splits | |
|---|---|---|---|---|---|---|---|---|---|
| count | 66355.000000 | 66355.000000 | 66355.000000 | 66355.000000 | 66355.000000 | 66355.000000 | 6.635500e+04 | 66355.000000 | 66355.000000 |
| mean | 33179.733102 | 197.040520 | 199.364124 | 194.585782 | 197.034900 | 190.494909 | 5.942124e+06 | 0.011757 | 0.000172 |
| std | 19158.201385 | 363.150484 | 367.873829 | 358.011643 | 363.052047 | 359.635301 | 1.615619e+07 | 0.283142 | 0.022716 |
| min | 1.000000 | 1.601000 | 1.662800 | 1.584200 | 1.606600 | 1.201300 | 0.000000e+00 | 0.000000 | 0.000000 |
| 25% | 16589.500000 | 29.789950 | 30.090000 | 29.470000 | 29.787450 | 28.143400 | 5.099855e+05 | 0.000000 | 0.000000 |
| 50% | 33178.000000 | 70.700000 | 71.400000 | 69.890000 | 70.680000 | 63.141000 | 1.415896e+06 | 0.000000 | 0.000000 |
| 75% | 49766.500000 | 185.990000 | 188.000000 | 184.000000 | 186.100000 | 175.253900 | 4.089299e+06 | 0.000000 | 0.000000 |
| max | 66930.000000 | 2926.000000 | 2957.000000 | 2813.000000 | 2839.000000 | 2802.938200 | 3.763915e+08 | 22.500000 | 5.000000 |
display(Markdown("**Include all dtypes:**"))
display(ohlcv_pd.describe(include="all"))Include all dtypes
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 66355.000000 | 66355 | 66355 | 66355.000000 | 66355.000000 | 66355.000000 | 66355.000000 | 66355.000000 | 6.635500e+04 | 66355.000000 | 66355.000000 | 66355 |
| unique | NaN | 50 | 1331 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2 |
| top | NaN | ABI.BR | 2026-03-12 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | False |
| freq | NaN | 1331 | 50 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 66349 |
| mean | 33179.733102 | NaN | NaN | 197.040520 | 199.364124 | 194.585782 | 197.034900 | 190.494909 | 5.942124e+06 | 0.011757 | 0.000172 | NaN |
| std | 19158.201385 | NaN | NaN | 363.150484 | 367.873829 | 358.011643 | 363.052047 | 359.635301 | 1.615619e+07 | 0.283142 | 0.022716 | NaN |
| min | 1.000000 | NaN | NaN | 1.601000 | 1.662800 | 1.584200 | 1.606600 | 1.201300 | 0.000000e+00 | 0.000000 | 0.000000 | NaN |
| 25% | 16589.500000 | NaN | NaN | 29.789950 | 30.090000 | 29.470000 | 29.787450 | 28.143400 | 5.099855e+05 | 0.000000 | 0.000000 | NaN |
| 50% | 33178.000000 | NaN | NaN | 70.700000 | 71.400000 | 69.890000 | 70.680000 | 63.141000 | 1.415896e+06 | 0.000000 | 0.000000 | NaN |
| 75% | 49766.500000 | NaN | NaN | 185.990000 | 188.000000 | 184.000000 | 186.100000 | 175.253900 | 4.089299e+06 | 0.000000 | 0.000000 | NaN |
| max | 66930.000000 | NaN | NaN | 2926.000000 | 2957.000000 | 2813.000000 | 2839.000000 | 2802.938200 | 3.763915e+08 | 22.500000 | 5.000000 | NaN |
Pandas | info()
# display hlcv_pd.info() as dataframe
info_df = pd.DataFrame({
"Column": ohlcv_pd.columns,
"Non-Null": [ohlcv_pd[c].notna().sum() for c in ohlcv_pd.columns],
"Dtype": [ohlcv_pd[c].dtype for c in ohlcv_pd.columns],
})
display(info_df)| Column | Non-Null | Dtype | |
|---|---|---|---|
| 0 | id | 66355 | int64 |
| 1 | symbol | 66355 | object |
| 2 | date | 66355 | object |
| 3 | open | 66355 | float64 |
| 4 | high | 66355 | float64 |
| 5 | low | 66355 | float64 |
| 6 | close | 66355 | float64 |
| 7 | adj_close | 66355 | float64 |
| 8 | volume | 66355 | int64 |
| 9 | dividends | 66355 | float64 |
| 10 | stock_splits | 66355 | float64 |
| 11 | is_filled | 66355 | bool |
Pandas | dtypes
display(ohlcv_pd.dtypes)| 0 | |
|---|---|
| id | int64 |
| symbol | object |
| date | object |
| open | float64 |
| high | float64 |
| low | float64 |
| close | float64 |
| adj_close | float64 |
| volume | int64 |
| dividends | float64 |
| stock_splits | float64 |
| is_filled | bool |
Polars | shape
for name, df in [("ohlcv", ohlcv_pl), ("dim", dim_pl), ("scores", scores_pl)]:
print(f"{name:>10s}: {df.shape[0]:>8,} rows x {df.shape[1]:>3} cols")ohlcv: 66,355 rows x 12 cols dim: 169 rows x 26 cols scores: 466 rows x 36 cols
Polars | describe()
- Describe: Summary statistics: count, mean, std, min, max, quartiles.
display(Markdown("**Polars describe (all columns):**"))
display(ohlcv_pl.describe())Polars describe (all columns)
| statistic | id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | f64 | str | str | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 |
| count | 66355.0 | 66355 | 66355 | 66355.0 | 66355.0 | 66355.0 | 66355.0 | 66355.0 | 66355.0 | 66355.0 | 66355.0 | 66355.0 |
| null_count | 0.0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
| mean | 33179.733102 | null | 2023-08-05 00:56:42.354005 | 197.04052 | 199.364124 | 194.585782 | 197.0349 | 190.494909 | 5.9421e6 | 0.011757 | 0.000172 | 0.00009 |
| std | 19158.201385 | null | null | 363.150484 | 367.873829 | 358.011643 | 363.052047 | 359.635301 | 1.6156e7 | 0.283142 | 0.022716 | null |
| min | 1.0 | ABI.BR | 2021-01-04 | 1.601 | 1.6628 | 1.5842 | 1.6066 | 1.2013 | 0.0 | 0.0 | 0.0 | 0.0 |
| 25% | 16590.0 | null | 2022-04-20 | 29.79 | 30.09 | 29.47 | 29.7899 | 28.1461 | 509991.0 | 0.0 | 0.0 | null |
| 50% | 33178.0 | null | 2023-08-03 | 70.7 | 71.4 | 69.89 | 70.68 | 63.141 | 1.415896e6 | 0.0 | 0.0 | null |
| 75% | 49767.0 | null | 2024-11-19 | 186.0 | 188.0 | 184.0 | 186.1 | 175.2609 | 4.089463e6 | 0.0 | 0.0 | null |
| max | 66930.0 | WKL.AS | 2026-03-12 | 2926.0 | 2957.0 | 2813.0 | 2839.0 | 2802.9382 | 3.76391539e8 | 22.5 | 5.0 | 1.0 |
Polars | schema and dtypes
display(Markdown("**Schema dict:**"))
for col_name, dtype in ohlcv_pl.schema.items():
print(f" {col_name:<20s} {dtype}")Schema dict
id Int64 symbol String date Date open Float64 high Float64 low Float64 close Float64 adj_close Float64 volume Int64 dividends Float64 stock_splits Float64 is_filled Boolean
display(Markdown("**dtypes list:**"))
print(ohlcv_pl.dtypes)dtypes list
[Int64, String, Date, Float64, Float64, Float64, Float64, Float64, Int64, Float64, Float64, Boolean]
Value Counts / Unique / N-Unique
Understanding cardinality and frequency distribution of columns. Pandas uses .value_counts(), .nunique(), and .unique(). Polars uses .value_counts(), .n_unique(), and .unique().
Pandas | value_counts()
- Value Counts: Count occurrences of each unique value.
display(Markdown("**Top 10 tickers by row count:**"))
display(ohlcv_pd["symbol"].value_counts().head(10))Top 10 tickers by row count
| count | |
|---|---|
| symbol | |
| ABI.BR | 1331 |
| AD.AS | 1331 |
| ADYEN.AS | 1331 |
| AI.PA | 1331 |
| AIR.PA | 1331 |
| ARGX.BR | 1331 |
| ASML.AS | 1331 |
| CS.PA | 1331 |
| DG.PA | 1331 |
| BN.PA | 1331 |
Pandas | nunique() and unique()
display(Markdown("**Number of unique values per column:**"))
display(ohlcv_pd.nunique())Number of unique values per column
| 0 | |
|---|---|
| id | 66355 |
| symbol | 50 |
| date | 1331 |
| open | 29671 |
| high | 31651 |
| low | 31695 |
| close | 31505 |
| adj_close | 57739 |
| volume | 65199 |
| dividends | 216 |
| stock_splits | 6 |
| is_filled | 2 |
display(Markdown("**Unique tickers (first 10):**"))
print(ohlcv_pd["symbol"].unique()[:10])Unique tickers (first 10)
[‘ABI.BR’ ‘AD.AS’ ‘ADS.DE’ ‘ADYEN.AS’ ‘AI.PA’ ‘AIR.PA’ ‘ALV.DE’ ‘ARGX.BR’ ‘ASML.AS’ ‘BAS.DE’]
Polars | value_counts()
- Value Counts: Count occurrences of each unique value.
display(Markdown("**Top 10 tickers by row count:**"))
display(
ohlcv_pl.get_column("symbol")
.value_counts()
.sort("count", descending=True)
.head(10)
)Top 10 tickers by row count
| symbol | count |
|---|---|
| str | u32 |
| SAN.PA | 1331 |
| ADYEN.AS | 1331 |
| PRX.AS | 1331 |
| ARGX.BR | 1331 |
| BN.PA | 1331 |
| DSY.PA | 1331 |
| BNP.PA | 1331 |
| TTE.PA | 1331 |
| ASML.AS | 1331 |
| CS.PA | 1331 |
Polars | n_unique() and unique()
- N Unique: Count the number of distinct values.
display(Markdown("**n_unique per column:**"))
display(
ohlcv_pl.select(pl.all().n_unique())
)n_unique per column
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 |
| 66355 | 50 | 1331 | 29671 | 31651 | 31695 | 31505 | 57739 | 65199 | 216 | 6 | 2 |
display(Markdown("**Unique tickers (first 10):**"))
print(ohlcv_pl.get_column("symbol").unique().sort().head(10).to_list())Unique tickers (first 10)
[‘ABI.BR’, ‘AD.AS’, ‘ADS.DE’, ‘ADYEN.AS’, ‘AI.PA’, ‘AIR.PA’, ‘ALV.DE’, ‘ARGX.BR’, ‘ASML.AS’, ‘BAS.DE’]
Null / Missing Value Inspection
Null detection is critical for data quality. Pandas uses NaN (float) for missing values, so .isna() and .isnull() are equivalent. Polars uses native Arrow null — use .is_null() and .null_count().
Pandas vs Polars | Null representation
Pandas
.isna()detects bothNaNandNone. Polars.is_null()detects only Arrow null —NaNis a valid float value in Polars, not a null. Use.is_nan()to detectNaNspecifically in Polars float columns.
Pandas | isna() / isnull()
display(Markdown("**Null counts per column:**"))
display(scores_pd.isnull().sum())Null counts per column
| 0 | |
|---|---|
| id | 0 |
| _index | 0 |
| symbol | 0 |
| score_date | 0 |
| sector | 0 |
| pe_zscore | 3 |
| pb_zscore | 6 |
| ev_ebitda_zscore | 71 |
| yield_zscore | 35 |
| relative_value_score | 0 |
| relative_value_rank | 0 |
| relative_strength | 0 |
| sma_50_ratio | 0 |
| sma_200_ratio | 0 |
| dist_from_52w_high | 0 |
| momentum_score | 0 |
| momentum_rank | 0 |
| implied_upside | 0 |
| recommendation_mean | 14 |
| price_falling_analysts_bullish | 0 |
| sentiment_score | 0 |
| sentiment_rank | 0 |
| composite_score | 0 |
| composite_rank | 0 |
| _scored_at | 0 |
| sma_30_close | 0 |
| sma_90_close | 0 |
| market_cap | 0 |
| index_weight | 0 |
| short_name | 0 |
| country | 0 |
| current_price | 0 |
| day_change_pct | 0 |
| five_day_change_pct | 0 |
| ytd_change_pct | 0 |
| currency | 0 |
display(Markdown("**Null percentage per column:**"))
null_pct = (scores_pd.isnull().sum() / len(scores_pd) * 100).round(2)
display(null_pct[null_pct > 0])Null percentage per column
| 0 | |
|---|---|
| pe_zscore | 0.64 |
| pb_zscore | 1.29 |
| ev_ebitda_zscore | 15.24 |
| yield_zscore | 7.51 |
| recommendation_mean | 3.00 |
Pandas | rows with any null
rows_with_nulls = scores_pd[scores_pd.isnull().any(axis=1)]
print(f"Rows with at least one null: {len(rows_with_nulls):,}")
if len(rows_with_nulls) > 0:
display(rows_with_nulls.head())Rows with at least one null: 120
| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.261140 | NaN | 2.388962 | 1.521163 | 1 | 0.016123 | 1.009090 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | False | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085000 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.320 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 5 | 196 | euro_stoxx_50 | VOW.DE | 2026-03-04 | Consumer Cyclical | 1.166221 | 0.940273 | 0.37983 | 1.528891 | 1.003804 | 2 | -0.292748 | 0.929392 | 0.969628 | 0.180805 | -0.411969 | 37 | 0.297071 | NaN | False | 0.555357 | 12 | 0.382397 | 6 | 2026-03-04 22:40:25.489180 | 102.286667 | 101.225556 | 47923826688 | 0.009381 | VOLKSWAGEN AG | Germany | 95.600 | 0.013786 | -0.048756 | -0.090390 | EUR |
| 8 | 188 | euro_stoxx_50 | SAN.MC | 2026-03-04 | Financial Services | 0.458599 | 0.499398 | NaN | -1.107590 | -0.049864 | 33 | 0.393197 | 0.953824 | 1.139519 | 0.113499 | 0.519307 | 14 | 0.224704 | 1.70000 | False | 0.448776 | 15 | 0.306073 | 9 | 2026-03-04 22:40:25.489180 | 10.604900 | 9.919500 | 145955749888 | 0.028570 | BANCO SANTANDER S.A. | Spain | 9.982 | 0.038818 | -0.105876 | -0.008739 | EUR |
| 15 | 176 | euro_stoxx_50 | ISP.MI | 2026-03-04 | Financial Services | 0.366619 | 0.488302 | NaN | 0.723361 | 0.526094 | 13 | -0.065247 | 0.922492 | 0.990855 | 0.119662 | -0.122626 | 33 | 0.254150 | 2.00000 | False | 0.146959 | 21 | 0.183476 | 16 | 2026-03-04 22:40:25.489180 | 5.834933 | 5.769367 | 94268317696 | 0.018452 | INTESA SANPAOLO | Italy | 5.422 | 0.018216 | -0.067103 | -0.084276 | EUR |
| 16 | 195 | euro_stoxx_50 | UCG.MI | 2026-03-04 | Financial Services | 0.452501 | 0.355225 | NaN | -0.260674 | 0.182351 | 26 | 0.085867 | 0.950889 | 1.054430 | 0.137862 | 0.123670 | 22 | 0.261521 | 1.94444 | False | 0.240819 | 18 | 0.182280 | 17 | 2026-03-04 22:40:25.489180 | 73.253333 | 68.983556 | 103066501120 | 0.020174 | UNICREDIT | Italy | 68.790 | 0.027483 | -0.072161 | -0.030034 | EUR |
Polars | is_null() / null_count()
- Null Count: Count missing values per column.
display(Markdown("**Null counts per column (Polars):**"))
display(scores_pl.null_count())Null counts per column (Polars)
| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 |
| 0 | 0 | 0 | 0 | 0 | 3 | 6 | 71 | 35 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 14 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
display(Markdown("**Null percentage per column (Polars):**"))
display(
scores_pl.select(
(pl.all().null_count() / pl.len() * 100).round(2).name.suffix("_null_pct")
)
)Null percentage per column (Polars)
| id_null_pct | _index_null_pct | symbol_null_pct | score_date_null_pct | sector_null_pct | pe_zscore_null_pct | pb_zscore_null_pct | ev_ebitda_zscore_null_pct | yield_zscore_null_pct | relative_value_score_null_pct | relative_value_rank_null_pct | relative_strength_null_pct | sma_50_ratio_null_pct | sma_200_ratio_null_pct | dist_from_52w_high_null_pct | momentum_score_null_pct | momentum_rank_null_pct | implied_upside_null_pct | recommendation_mean_null_pct | price_falling_analysts_bullish_null_pct | sentiment_score_null_pct | sentiment_rank_null_pct | composite_score_null_pct | composite_rank_null_pct | _scored_at_null_pct | sma_30_close_null_pct | sma_90_close_null_pct | market_cap_null_pct | index_weight_null_pct | short_name_null_pct | country_null_pct | current_price_null_pct | day_change_pct_null_pct | five_day_change_pct_null_pct | ytd_change_pct_null_pct | currency_null_pct |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 |
| 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.64 | 1.29 | 15.24 | 7.51 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 3.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 |
Polars | rows with any null
mask = pl.any_horizontal(pl.all().is_null())
rows_with_nulls_pl = scores_pl.filter(mask)
print(f"Rows with at least one null: {rows_with_nulls_pl.shape[0]:,}")
if rows_with_nulls_pl.shape[0] > 0:
display(rows_with_nulls_pl.head())Rows with at least one null: 120
| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | str | date | str | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool | f64 | i64 | f64 | i64 | datetime[ns] | f64 | f64 | i64 | f64 | str | str | f64 | f64 | f64 | f64 | str |
| 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.26114 | null | 2.388962 | 1.521163 | 1 | 0.016123 | 1.00909 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | false | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.32 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 196 | euro_stoxx_50 | VOW.DE | 2026-03-04 | Consumer Cyclical | 1.166221 | 0.940273 | 0.37983 | 1.528891 | 1.003804 | 2 | -0.292748 | 0.929392 | 0.969628 | 0.180805 | -0.411969 | 37 | 0.297071 | null | false | 0.555357 | 12 | 0.382397 | 6 | 2026-03-04 22:40:25.489180 | 102.286667 | 101.225556 | 47923826688 | 0.009381 | VOLKSWAGEN AG | Germany | 95.6 | 0.013786 | -0.048756 | -0.09039 | EUR |
| 188 | euro_stoxx_50 | SAN.MC | 2026-03-04 | Financial Services | 0.458599 | 0.499398 | null | -1.10759 | -0.049864 | 33 | 0.393197 | 0.953824 | 1.139519 | 0.113499 | 0.519307 | 14 | 0.224704 | 1.7 | false | 0.448776 | 15 | 0.306073 | 9 | 2026-03-04 22:40:25.489180 | 10.6049 | 9.9195 | 145955749888 | 0.02857 | BANCO SANTANDER S.A. | Spain | 9.982 | 0.038818 | -0.105876 | -0.008739 | EUR |
| 176 | euro_stoxx_50 | ISP.MI | 2026-03-04 | Financial Services | 0.366619 | 0.488302 | null | 0.723361 | 0.526094 | 13 | -0.065247 | 0.922492 | 0.990855 | 0.119662 | -0.122626 | 33 | 0.25415 | 2.0 | false | 0.146959 | 21 | 0.183476 | 16 | 2026-03-04 22:40:25.489180 | 5.834933 | 5.769367 | 94268317696 | 0.018452 | INTESA SANPAOLO | Italy | 5.422 | 0.018216 | -0.067103 | -0.084276 | EUR |
| 195 | euro_stoxx_50 | UCG.MI | 2026-03-04 | Financial Services | 0.452501 | 0.355225 | null | -0.260674 | 0.182351 | 26 | 0.085867 | 0.950889 | 1.05443 | 0.137862 | 0.12367 | 22 | 0.261521 | 1.94444 | false | 0.240819 | 18 | 0.18228 | 17 | 2026-03-04 22:40:25.489180 | 73.253333 | 68.983556 | 103066501120 | 0.020174 | UNICREDIT | Italy | 68.79 | 0.027483 | -0.072161 | -0.030034 | EUR |
Exploring the Scores Dataset
The scores_daily dataset contains composite factor scores with real null values in several columns — ideal for practicing null inspection and data quality assessment.
Pandas | quick profile
display(Markdown("**scores_daily — head:**"))
display(scores_pd.head())scores_daily — head
| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.261140 | NaN | 2.388962 | 1.521163 | 1 | 0.016123 | 1.009090 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | False | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085000 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.320 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 1 | 168 | euro_stoxx_50 | DTE.DE | 2026-03-04 | Communication Services | 0.326587 | 0.387463 | 0.379532 | -0.127867 | 0.241429 | 24 | -0.205598 | 1.120650 | 1.112416 | 0.055524 | 0.685752 | 8 | 0.121212 | 1.33333 | False | 0.617835 | 10 | 0.515005 | 2 | 2026-03-04 22:40:25.489180 | 30.838000 | 28.554556 | 164294311936 | 0.032159 | DEUTSCHE TELEKOM AG | Germany | 33.000 | 0.011649 | -0.019608 | 0.193059 | EUR |
| 2 | 174 | euro_stoxx_50 | IFX.DE | 2026-03-04 | Technology | 0.509398 | 0.637215 | 0.677068 | -0.696662 | 0.281755 | 22 | 0.000965 | 1.048244 | 1.198626 | 0.088845 | 0.675764 | 9 | 0.126408 | 1.37500 | False | 0.579187 | 11 | 0.512235 | 3 | 2026-03-04 22:40:25.489180 | 43.480333 | 38.855556 | 57222533120 | 0.011201 | INFINEON TECHNOLOGIES AG | Germany | 43.945 | 0.054343 | -0.066490 | 0.164723 | EUR |
| 3 | 172 | euro_stoxx_50 | ENR.DE | 2026-03-04 | Industrials | -0.902738 | -0.743338 | -1.693212 | -1.326075 | -1.166341 | 46 | 1.645455 | 1.137007 | 1.474095 | 0.051850 | 2.541889 | 1 | 0.075269 | 1.80000 | False | -0.123264 | 29 | 0.417428 | 4 | 2026-03-04 22:40:25.489180 | 155.675000 | 129.122000 | 139207262208 | 0.027249 | Siemens Energy AG | Germany | 162.750 | 0.047297 | -0.039256 | 0.351744 | EUR |
| 4 | 149 | euro_stoxx_50 | ABI.BR | 2026-03-04 | Consumer Defensive | 0.474084 | 0.844075 | 0.552739 | -0.975005 | 0.223973 | 25 | -0.029791 | 1.058542 | 1.142783 | 0.063063 | 0.651891 | 10 | 0.186198 | 1.69231 | False | 0.344755 | 17 | 0.406873 | 5 | 2026-03-04 22:40:25.489180 | 64.342000 | 57.869333 | 125566156800 | 0.024579 | AB INBEV | Belgium | 64.480 | -0.017073 | -0.040762 | 0.174499 | EUR |
display(Markdown("**scores_daily — describe:**"))
display(scores_pd.describe(include="all"))scores_daily — describe
| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 466.000000 | 466 | 466 | 466 | 466 | 463.000000 | 460.000000 | 395.000000 | 431.000000 | 466.000000 | 466.000000 | 466.000000 | 466.000000 | 466.000000 | 466.000000 | 466.000000 | 466.000000 | 466.000000 | 452.000000 | 466 | 466.000000 | 466.000000 | 466.000000 | 466.000000 | 466 | 466.000000 | 466.000000 | 4.660000e+02 | 466.000000 | 466 | 466 | 466.000000 | 466.000000 | 466.000000 | 466.000000 | 466 |
| unique | NaN | 4 | 167 | 3 | 10 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 2 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 167 | 16 | NaN | NaN | NaN | NaN | 6 |
| top | NaN | stoxx_asia_50 | CVX | 2026-03-12 | Financial Services | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | False | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | Chevron Corporation | United States | NaN | NaN | NaN | NaN | USD |
| freq | NaN | 150 | 4 | 169 | 96 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 401 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 4 | 159 | NaN | NaN | NaN | NaN | 167 |
| mean | 2083.712446 | NaN | NaN | NaN | NaN | 0.011386 | 0.024513 | 0.038046 | 0.051915 | 0.036204 | 24.733906 | 0.047972 | 0.991152 | 1.064981 | 0.154695 | -0.000157 | 24.768240 | 0.175049 | 2.011047 | NaN | -0.004157 | 24.793991 | 0.010630 | 24.774678 | 2026-03-08 09:35:56.501645824 | 2726.980722 | 2568.225890 | 3.030703e+12 | 0.021271 | NaN | NaN | 2721.358232 | -0.001651 | -0.024835 | 0.028935 | NaN |
| min | 149.000000 | NaN | NaN | NaN | NaN | -3.244355 | -3.609853 | -3.562197 | -1.765215 | -3.069151 | 1.000000 | -0.758111 | 0.764715 | 0.630163 | 0.000686 | -2.270682 | 1.000000 | -0.287205 | 1.224490 | NaN | -3.028190 | 1.000000 | -1.265925 | 1.000000 | 2026-03-04 22:40:25.489180 | 4.967667 | 4.912000 | 1.516982e+10 | 0.000102 | NaN | NaN | 5.120000 | -0.071643 | -0.147762 | -0.326764 | NaN |
| 25% | 266.250000 | NaN | NaN | NaN | NaN | -0.544030 | -0.360416 | -0.292057 | -0.680445 | -0.327241 | 12.000000 | -0.207124 | 0.928823 | 0.946901 | 0.073301 | -0.442542 | 12.000000 | 0.070549 | 1.666670 | NaN | -0.632834 | 12.000000 | -0.175500 | 12.000000 | 2026-03-04 22:40:25.489179904 | 71.671500 | 72.912944 | 9.705579e+10 | 0.007191 | NaN | NaN | 69.900000 | -0.013312 | -0.054816 | -0.083552 | NaN |
| 50% | 2387.500000 | NaN | NaN | NaN | NaN | 0.294467 | 0.328987 | 0.309876 | 0.051261 | 0.178100 | 24.000000 | -0.027879 | 0.984705 | 1.058055 | 0.124107 | 0.029804 | 24.000000 | 0.152967 | 1.944440 | NaN | 0.023661 | 24.000000 | 0.031939 | 24.000000 | 2026-03-07 03:00:57.486865920 | 206.003000 | 202.193333 | 2.577942e+11 | 0.012848 | NaN | NaN | 203.470000 | -0.002397 | -0.023506 | 0.019151 | NaN |
| 75% | 3410.750000 | NaN | NaN | NaN | NaN | 0.643742 | 0.617103 | 0.622976 | 0.718698 | 0.523516 | 37.000000 | 0.197873 | 1.046022 | 1.177306 | 0.200560 | 0.488433 | 37.000000 | 0.273151 | 2.280000 | NaN | 0.527927 | 37.000000 | 0.243820 | 37.000000 | 2026-03-12 12:52:26.509884928 | 856.826917 | 837.629000 | 1.628276e+12 | 0.027448 | NaN | NaN | 799.057500 | 0.009959 | 0.002131 | 0.129549 | NaN |
| max | 3527.000000 | NaN | NaN | NaN | NaN | 1.666177 | 2.019105 | 1.697527 | 2.919889 | 1.521163 | 50.000000 | 3.342994 | 1.233011 | 1.905060 | 0.589001 | 2.806741 | 50.000000 | 0.804817 | 4.785710 | NaN | 2.169723 | 50.000000 | 1.287144 | 50.000000 | 2026-03-12 12:52:26.509885 | 68837.666667 | 60523.222222 | 4.587752e+13 | 0.245721 | NaN | NaN | 69950.000000 | 0.091834 | 0.192987 | 0.466977 | NaN |
| std | 1340.387660 | NaN | NaN | NaN | NaN | 0.894679 | 0.922349 | 0.875291 | 0.914714 | 0.723838 | 14.457279 | 0.462482 | 0.081733 | 0.182469 | 0.117997 | 0.817166 | 14.472144 | 0.172802 | 0.512925 | NaN | 0.886008 | 14.458262 | 0.336210 | 14.470685 | NaN | 9736.666450 | 8945.961901 | 6.558209e+12 | 0.024398 | NaN | NaN | 9820.254826 | 0.022118 | 0.046550 | 0.151246 | NaN |
display(Markdown("**scores_daily — null counts:**"))
display(scores_pd.isnull().sum())scores_daily — null counts
| 0 | |
|---|---|
| id | 0 |
| _index | 0 |
| symbol | 0 |
| score_date | 0 |
| sector | 0 |
| pe_zscore | 3 |
| pb_zscore | 6 |
| ev_ebitda_zscore | 71 |
| yield_zscore | 35 |
| relative_value_score | 0 |
| relative_value_rank | 0 |
| relative_strength | 0 |
| sma_50_ratio | 0 |
| sma_200_ratio | 0 |
| dist_from_52w_high | 0 |
| momentum_score | 0 |
| momentum_rank | 0 |
| implied_upside | 0 |
| recommendation_mean | 14 |
| price_falling_analysts_bullish | 0 |
| sentiment_score | 0 |
| sentiment_rank | 0 |
| composite_score | 0 |
| composite_rank | 0 |
| _scored_at | 0 |
| sma_30_close | 0 |
| sma_90_close | 0 |
| market_cap | 0 |
| index_weight | 0 |
| short_name | 0 |
| country | 0 |
| current_price | 0 |
| day_change_pct | 0 |
| five_day_change_pct | 0 |
| ytd_change_pct | 0 |
| currency | 0 |
Polars | quick profile
display(Markdown("**scores_daily — head (Polars):**"))
display(scores_pl.head())scores_daily — head (Polars)
| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | str | date | str | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool | f64 | i64 | f64 | i64 | datetime[ns] | f64 | f64 | i64 | f64 | str | str | f64 | f64 | f64 | f64 | str |
| 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.26114 | null | 2.388962 | 1.521163 | 1 | 0.016123 | 1.00909 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | false | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.32 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 168 | euro_stoxx_50 | DTE.DE | 2026-03-04 | Communication Services | 0.326587 | 0.387463 | 0.379532 | -0.127867 | 0.241429 | 24 | -0.205598 | 1.12065 | 1.112416 | 0.055524 | 0.685752 | 8 | 0.121212 | 1.33333 | false | 0.617835 | 10 | 0.515005 | 2 | 2026-03-04 22:40:25.489180 | 30.838 | 28.554556 | 164294311936 | 0.032159 | DEUTSCHE TELEKOM AG | Germany | 33.0 | 0.011649 | -0.019608 | 0.193059 | EUR |
| 174 | euro_stoxx_50 | IFX.DE | 2026-03-04 | Technology | 0.509398 | 0.637215 | 0.677068 | -0.696662 | 0.281755 | 22 | 0.000965 | 1.048244 | 1.198626 | 0.088845 | 0.675764 | 9 | 0.126408 | 1.375 | false | 0.579187 | 11 | 0.512235 | 3 | 2026-03-04 22:40:25.489180 | 43.480333 | 38.855556 | 57222533120 | 0.011201 | INFINEON TECHNOLOGIES AG | Germany | 43.945 | 0.054343 | -0.06649 | 0.164723 | EUR |
| 172 | euro_stoxx_50 | ENR.DE | 2026-03-04 | Industrials | -0.902738 | -0.743338 | -1.693212 | -1.326075 | -1.166341 | 46 | 1.645455 | 1.137007 | 1.474095 | 0.05185 | 2.541889 | 1 | 0.075269 | 1.8 | false | -0.123264 | 29 | 0.417428 | 4 | 2026-03-04 22:40:25.489180 | 155.675 | 129.122 | 139207262208 | 0.027249 | Siemens Energy AG | Germany | 162.75 | 0.047297 | -0.039256 | 0.351744 | EUR |
| 149 | euro_stoxx_50 | ABI.BR | 2026-03-04 | Consumer Defensive | 0.474084 | 0.844075 | 0.552739 | -0.975005 | 0.223973 | 25 | -0.029791 | 1.058542 | 1.142783 | 0.063063 | 0.651891 | 10 | 0.186198 | 1.69231 | false | 0.344755 | 17 | 0.406873 | 5 | 2026-03-04 22:40:25.489180 | 64.342 | 57.869333 | 125566156800 | 0.024579 | AB INBEV | Belgium | 64.48 | -0.017073 | -0.040762 | 0.174499 | EUR |
display(Markdown("**scores_daily — describe (Polars):**"))
display(scores_pl.describe())scores_daily — describe (Polars)
| statistic | id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | f64 | str | str | str | str | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | f64 | str | f64 | f64 | f64 | f64 | str | str | f64 | f64 | f64 | f64 | str |
| count | 466.0 | 466 | 466 | 466 | 466 | 463.0 | 460.0 | 395.0 | 431.0 | 466.0 | 466.0 | 466.0 | 466.0 | 466.0 | 466.0 | 466.0 | 466.0 | 466.0 | 452.0 | 466.0 | 466.0 | 466.0 | 466.0 | 466.0 | 466 | 466.0 | 466.0 | 466.0 | 466.0 | 466 | 466 | 466.0 | 466.0 | 466.0 | 466.0 | 466 |
| null_count | 0.0 | 0 | 0 | 0 | 0 | 3.0 | 6.0 | 71.0 | 35.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 14.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 | 0 |
| mean | 2083.712446 | null | null | 2026-03-07 20:48:24.721030 | null | 0.011386 | 0.024513 | 0.038046 | 0.051915 | 0.036204 | 24.733906 | 0.047972 | 0.991152 | 1.064981 | 0.154695 | -0.000157 | 24.76824 | 0.175049 | 2.011047 | 0.139485 | -0.004157 | 24.793991 | 0.01063 | 24.774678 | 2026-03-08 09:35:56.501646 | 2726.980722 | 2568.22589 | 3.0307e12 | 0.021271 | null | null | 2721.358232 | -0.001651 | -0.024835 | 0.028935 | null |
| std | 1340.38766 | null | null | null | null | 0.894679 | 0.922349 | 0.875291 | 0.914714 | 0.723838 | 14.457279 | 0.462482 | 0.081733 | 0.182469 | 0.117997 | 0.817166 | 14.472144 | 0.172802 | 0.512925 | null | 0.886008 | 14.458262 | 0.33621 | 14.470685 | null | 9736.66645 | 8945.961901 | 6.5582e12 | 0.024398 | null | null | 9820.254826 | 0.022118 | 0.04655 | 0.151246 | null |
| min | 149.0 | euro_stoxx_50 | 0388.HK | 2026-03-04 | Basic Materials | -3.244355 | -3.609853 | -3.562197 | -1.765215 | -3.069151 | 1.0 | -0.758111 | 0.764715 | 0.630163 | 0.000686 | -2.270682 | 1.0 | -0.287205 | 1.22449 | 0.0 | -3.02819 | 1.0 | -1.265925 | 1.0 | 2026-03-04 22:40:25.489180 | 4.967667 | 4.912 | 1.5170e10 | 0.000102 | AB INBEV | Australia | 5.12 | -0.071643 | -0.147762 | -0.326764 | AUD |
| 25% | 266.0 | null | null | 2026-03-04 | null | -0.542352 | -0.358338 | -0.285053 | -0.678854 | -0.327398 | 12.0 | -0.207295 | 0.928634 | 0.94669 | 0.073233 | -0.443075 | 12.0 | 0.070336 | 1.66667 | null | -0.635563 | 12.0 | -0.176007 | 12.0 | 2026-03-04 22:40:25.489179 | 71.527 | 72.844222 | 9.7039e10 | 0.00719 | null | null | 69.8 | -0.013336 | -0.054849 | -0.083791 | null |
| 50% | 2388.0 | null | null | 2026-03-07 | null | 0.294467 | 0.32996 | 0.309876 | 0.051261 | 0.179359 | 24.0 | -0.025968 | 0.984787 | 1.058079 | 0.124181 | 0.031184 | 24.0 | 0.153157 | 1.94444 | null | 0.024154 | 24.0 | 0.032344 | 24.0 | 2026-03-07 03:00:57.486865 | 207.221667 | 204.365333 | 2.5787e11 | 0.013111 | null | null | 204.83 | -0.002381 | -0.023153 | 0.019395 | null |
| 75% | 3411.0 | null | null | 2026-03-12 | null | 0.645507 | 0.616745 | 0.625587 | 0.723361 | 0.526094 | 37.0 | 0.199916 | 1.046095 | 1.17734 | 0.200735 | 0.489412 | 37.0 | 0.273617 | 2.28 | null | 0.528756 | 37.0 | 0.24484 | 37.0 | 2026-03-12 12:52:26.509884 | 900.273667 | 874.914222 | 1.6312e12 | 0.027458 | null | null | 821.42 | 0.009963 | 0.002179 | 0.12961 | null |
| max | 3527.0 | stoxx_usa_50 | XOM | 2026-03-12 | Utilities | 1.666177 | 2.019105 | 1.697527 | 2.919889 | 1.521163 | 50.0 | 3.342994 | 1.233011 | 1.90506 | 0.589001 | 2.806741 | 50.0 | 0.804817 | 4.78571 | 1.0 | 2.169723 | 50.0 | 1.287144 | 50.0 | 2026-03-12 12:52:26.509885 | 68837.666667 | 60523.222222 | 4.5878e13 | 0.245721 | adidas AG | United States | 69950.0 | 0.091834 | 0.192987 | 0.466977 | USD |
display(Markdown("**scores_daily — null counts (Polars):**"))
display(scores_pl.null_count())scores_daily — null counts (Polars)
| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 | u32 |
| 0 | 0 | 0 | 0 | 0 | 3 | 6 | 71 | 35 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 14 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Exploring the Dimension Table
Head Preview
display(Markdown("**index_dim — Pandas:**"))
display(dim_pd.drop(columns="long_business_summary").head())index_dim — Pandas
| id | _index | symbol | long_name | short_name | sector | sector_key | industry | industry_key | country | city | website | exchange | full_exchange_name | exchange_timezone_name | exchange_timezone_short | currency | financial_currency | quote_type | market | range_start | price_data_start | valid_from | valid_to | is_current | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | euro_stoxx_50 | ASML.AS | ASML Holding N.V. | ASML HOLDING | Technology | technology | Semiconductor Equipment & Materials | semiconductor-equipment-materials | Netherlands | Veldhoven | https://www.asml.com | AMS | Amsterdam | Europe/Amsterdam | CET | EUR | EUR | EQUITY | nl_market | 1998-07-20 | 2021-01-01 | 2026-03-04 22:11:36.189862 | None | True |
| 1 | 2 | euro_stoxx_50 | MC.PA | LVMH Moët Hennessy - Louis Vuitton, Société Européenne | LVMH | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://www.lvmh.com | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | 2026-03-04 22:11:36.189862 | None | True |
| 2 | 3 | euro_stoxx_50 | RMS.PA | Hermès International Société en commandite par actions | HERMES INTL | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://finance.hermes.com | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | 2026-03-04 22:11:36.193940 | None | True |
| 3 | 4 | euro_stoxx_50 | OR.PA | L'Oréal S.A. | L'OREAL | Consumer Defensive | consumer-defensive | Household & Personal Products | household-personal-products | France | Clichy | https://www.loreal.com | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | 2026-03-04 22:11:36.193940 | None | True |
| 4 | 5 | euro_stoxx_50 | SAP.DE | SAP SE | SAP SE | Technology | technology | Software - Application | software-application | Germany | Walldorf | https://www.sap.com | GER | XETRA | Europe/Berlin | CET | EUR | EUR | EQUITY | de_market | 1998-04-09 | 2021-01-01 | 2026-03-04 22:11:36.193940 | None | True |
display(Markdown("**index_dim — Polars:**"))
display(dim_pl.head())index_dim — Polars
| id | _index | symbol | long_name | short_name | sector | sector_key | industry | industry_key | country | city | website | long_business_summary | exchange | full_exchange_name | exchange_timezone_name | exchange_timezone_short | currency | financial_currency | quote_type | market | range_start | price_data_start | valid_from | valid_to | is_current |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | date | date | datetime[ns] | null | bool |
| 1 | euro_stoxx_50 | ASML.AS | ASML Holding N.V. | ASML HOLDING | Technology | technology | Semiconductor Equipment & Mate… | semiconductor-equipment-materi… | Netherlands | Veldhoven | https://www.asml.com | ASML Holding N.V. provides lit… | AMS | Amsterdam | Europe/Amsterdam | CET | EUR | EUR | EQUITY | nl_market | 1998-07-20 | 2021-01-01 | 2026-03-04 22:11:36.189862 | null | true |
| 2 | euro_stoxx_50 | MC.PA | LVMH Moët Hennessy - Louis Vui… | LVMH | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://www.lvmh.com | LVMH Moët Hennessy - Louis Vui… | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | 2026-03-04 22:11:36.189862 | null | true |
| 3 | euro_stoxx_50 | RMS.PA | Hermès International Société e… | HERMES INTL | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://finance.hermes.com | Hermès International Société e… | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | 2026-03-04 22:11:36.193940 | null | true |
| 4 | euro_stoxx_50 | OR.PA | L'Oréal S.A. | L'OREAL | Consumer Defensive | consumer-defensive | Household & Personal Products | household-personal-products | France | Clichy | https://www.loreal.com | L'Oréal S.A., through its subs… | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | 2026-03-04 22:11:36.193940 | null | true |
| 5 | euro_stoxx_50 | SAP.DE | SAP SE | SAP SE | Technology | technology | Software - Application | software-application | Germany | Walldorf | https://www.sap.com | SAP SE, together with its subs… | GER | XETRA | Europe/Berlin | CET | EUR | EUR | EQUITY | de_market | 1998-04-09 | 2021-01-01 | 2026-03-04 22:11:36.193940 | null | true |
Types and Schema
display(Markdown("**index_dim — dtypes (Pandas):**"))
display(dim_pd.dtypes)index_dim — dtypes (Pandas)
| 0 | |
|---|---|
| id | int64 |
| _index | object |
| symbol | object |
| long_name | object |
| short_name | object |
| sector | object |
| sector_key | object |
| industry | object |
| industry_key | object |
| country | object |
| city | object |
| website | object |
| long_business_summary | object |
| exchange | object |
| full_exchange_name | object |
| exchange_timezone_name | object |
| exchange_timezone_short | object |
| currency | object |
| financial_currency | object |
| quote_type | object |
| market | object |
| range_start | object |
| price_data_start | object |
| valid_from | datetime64[ns] |
| valid_to | object |
| is_current | bool |
display(Markdown("**index_dim — schema (Polars):**"))
for col_name, dtype in dim_pl.schema.items():
print(f" {col_name:<25s} {dtype}")index_dim — schema (Polars)
id Int64 _index String symbol String long_name String short_name String sector String sector_key String industry String industry_key String country String city String website String long_business_summary String exchange String full_exchange_name String exchange_timezone_name String exchange_timezone_short String currency String financial_currency String quote_type String market String range_start Date price_data_start Date valid_from Datetime(time_unit=‘ns’, time_zone=None) valid_to Null is_current Boolean
Data Profiling Strategies
Reusable profiling functions that combine shape, types, null counts, and basic stats into a single summary. Build these once and apply to any dataset.
A reusable profiling pattern: for each column report dtype, null count, unique count, and a few sample values.
Pandas | quick profiler
- Drop Nulls: Remove rows with missing values (Pandas).
- N Unique: Count the number of distinct values.
def profile_pd(df: pd.DataFrame) -> pd.DataFrame:
"""Return a one-row-per-column profiling DataFrame (Pandas)."""
records = []
for col in df.columns:
records.append({
"column": col,
"dtype": str(df[col].dtype),
"null_count": int(df[col].isnull().sum()),
"null_pct": round(df[col].isnull().mean() * 100, 2),
"n_unique": int(df[col].nunique()),
"sample": str(df[col].dropna().iloc[:3].tolist()),
})
return pd.DataFrame(records)
display(Markdown("**OHLCV profile (Pandas):**"))
display(profile_pd(ohlcv_pd))OHLCV profile (Pandas)
| column | dtype | null_count | null_pct | n_unique | sample | |
|---|---|---|---|---|---|---|
| 0 | id | int64 | 0 | 0.0 | 66355 | [21160, 21161, 21162] |
| 1 | symbol | object | 0 | 0.0 | 50 | ['ABI.BR', 'ABI.BR', 'ABI.BR'] |
| 2 | date | datetime64[ns] | 0 | 0.0 | 1331 | [Timestamp('2021-01-04 00:00:00'), Timestamp('2021-01-05 00:00:00'), Timestamp('2021-01-06 00:00:00')] |
| 3 | open | float64 | 0 | 0.0 | 29671 | [58.15, 56.9, 57.96] |
| 4 | high | float64 | 0 | 0.0 | 31651 | [58.85, 57.98, 58.94] |
| 5 | low | float64 | 0 | 0.0 | 31695 | [56.78, 56.75, 57.39] |
| 6 | close | float64 | 0 | 0.0 | 31505 | [57.21, 57.18, 58.77] |
| 7 | adj_close | float64 | 0 | 0.0 | 57739 | [53.5761, 53.548, 55.037] |
| 8 | volume | int64 | 0 | 0.0 | 65199 | [1513937, 1382722, 1370204] |
| 9 | dividends | float64 | 0 | 0.0 | 216 | [0.0, 0.0, 0.0] |
| 10 | stock_splits | float64 | 0 | 0.0 | 6 | [0.0, 0.0, 0.0] |
| 11 | is_filled | bool | 0 | 0.0 | 2 | [False, False, False] |
Polars | quick profiler
- Null Count: Count missing values per column.
- Drop Nulls: Remove rows with missing values (Polars).
- N Unique: Count the number of distinct values.
def profile_pl(df: pl.DataFrame) -> pl.DataFrame:
"""Return a one-row-per-column profiling DataFrame (Polars)."""
rows = []
for col_name in df.columns:
col = df.get_column(col_name)
rows.append({
"column": col_name,
"dtype": str(col.dtype),
"null_count": col.null_count(),
"null_pct": round(col.null_count() / df.height * 100, 2),
"n_unique": col.n_unique(),
"sample": str(col.drop_nulls().head(3).to_list()),
})
return pl.DataFrame(rows)
display(Markdown("**OHLCV profile (Polars):**"))
display(profile_pl(ohlcv_pl))OHLCV profile (Polars)
| column | dtype | null_count | null_pct | n_unique | sample |
|---|---|---|---|---|---|
| str | str | i64 | f64 | i64 | str |
| id | Int64 | 0 | 0.0 | 66355 | [21160, 21161, 21162] |
| symbol | String | 0 | 0.0 | 50 | ['ABI.BR', 'ABI.BR', 'ABI.BR'] |
| date | Date | 0 | 0.0 | 1331 | [datetime.date(2021, 1, 4), da… |
| open | Float64 | 0 | 0.0 | 29671 | [58.15, 56.9, 57.96] |
| high | Float64 | 0 | 0.0 | 31651 | [58.85, 57.98, 58.94] |
| low | Float64 | 0 | 0.0 | 31695 | [56.78, 56.75, 57.39] |
| close | Float64 | 0 | 0.0 | 31505 | [57.21, 57.18, 58.77] |
| adj_close | Float64 | 0 | 0.0 | 57739 | [53.5761, 53.548, 55.037] |
| volume | Int64 | 0 | 0.0 | 65199 | [1513937, 1382722, 1370204] |
| dividends | Float64 | 0 | 0.0 | 216 | [0.0, 0.0, 0.0] |
| stock_splits | Float64 | 0 | 0.0 | 6 | [0.0, 0.0, 0.0] |
| is_filled | Boolean | 0 | 0.0 | 2 | [False, False, False] |
Profile all three datasets
Profile eurostoxx50_ohlcv — Pandas vs Polars describe comparison
prof_pd = profile_pd(ohlcv_pd)
display(Markdown("*Pandas profile:*"))
display(prof_pd)Pandas profile:
| column | dtype | null_count | null_pct | n_unique | sample | |
|---|---|---|---|---|---|---|
| 0 | id | int64 | 0 | 0.0 | 66355 | [21160, 21161, 21162] |
| 1 | symbol | object | 0 | 0.0 | 50 | ['ABI.BR', 'ABI.BR', 'ABI.BR'] |
| 2 | date | object | 0 | 0.0 | 1331 | [datetime.date(2021, 1, 4), datetime.date(2021, 1, 5), datetime.date(2021, 1, 6)] |
| 3 | open | float64 | 0 | 0.0 | 29671 | [58.15, 56.9, 57.96] |
| 4 | high | float64 | 0 | 0.0 | 31651 | [58.85, 57.98, 58.94] |
| 5 | low | float64 | 0 | 0.0 | 31695 | [56.78, 56.75, 57.39] |
| 6 | close | float64 | 0 | 0.0 | 31505 | [57.21, 57.18, 58.77] |
| 7 | adj_close | float64 | 0 | 0.0 | 57739 | [53.5761, 53.548, 55.037] |
| 8 | volume | int64 | 0 | 0.0 | 65199 | [1513937, 1382722, 1370204] |
| 9 | dividends | float64 | 0 | 0.0 | 216 | [0.0, 0.0, 0.0] |
| 10 | stock_splits | float64 | 0 | 0.0 | 6 | [0.0, 0.0, 0.0] |
| 11 | is_filled | bool | 0 | 0.0 | 2 | [False, False, False] |
prof_pl = profile_pl(ohlcv_pl)
display(Markdown("*Polars profile:*"))
display(prof_pl)Polars profile:
| column | dtype | null_count | null_pct | n_unique | sample |
|---|---|---|---|---|---|
| str | str | i64 | f64 | i64 | str |
| id | Int64 | 0 | 0.0 | 66355 | [21160, 21161, 21162] |
| symbol | String | 0 | 0.0 | 50 | ['ABI.BR', 'ABI.BR', 'ABI.BR'] |
| date | Date | 0 | 0.0 | 1331 | [datetime.date(2021, 1, 4), da… |
| open | Float64 | 0 | 0.0 | 29671 | [58.15, 56.9, 57.96] |
| high | Float64 | 0 | 0.0 | 31651 | [58.85, 57.98, 58.94] |
| low | Float64 | 0 | 0.0 | 31695 | [56.78, 56.75, 57.39] |
| close | Float64 | 0 | 0.0 | 31505 | [57.21, 57.18, 58.77] |
| adj_close | Float64 | 0 | 0.0 | 57739 | [53.5761, 53.548, 55.037] |
| volume | Int64 | 0 | 0.0 | 65199 | [1513937, 1382722, 1370204] |
| dividends | Float64 | 0 | 0.0 | 216 | [0.0, 0.0, 0.0] |
| stock_splits | Float64 | 0 | 0.0 | 6 | [0.0, 0.0, 0.0] |
| is_filled | Boolean | 0 | 0.0 | 2 | [False, False, False] |
Profile index_dim — Pandas vs Polars describe comparison
prof_pd = profile_pd(dim_pd)
prof_pd = prof_pd[prof_pd["column"] != "long_business_summary"]
display(Markdown("*Pandas profile:*"))
display(prof_pd.head())Pandas profile:
| column | dtype | null_count | null_pct | n_unique | sample | |
|---|---|---|---|---|---|---|
| 0 | id | int64 | 0 | 0.0 | 169 | [1, 2, 3] |
| 1 | _index | object | 0 | 0.0 | 4 | ['euro_stoxx_50', 'euro_stoxx_50', 'euro_stoxx_50'] |
| 2 | symbol | object | 0 | 0.0 | 167 | ['ASML.AS', 'MC.PA', 'RMS.PA'] |
| 3 | long_name | object | 0 | 0.0 | 166 | ['ASML Holding N.V.', 'LVMH Moët Hennessy - Louis Vuitton, Société Européenne', 'Hermès International Société en commandite par actions'] |
| 4 | short_name | object | 0 | 0.0 | 167 | ['ASML HOLDING', 'LVMH', 'HERMES INTL'] |
prof_pl = profile_pl(dim_pl)
prof_pl = prof_pl.filter(pl.col("column") != "long_business_summary")
display(Markdown("*Polars profile:*"))
display(prof_pl.head())Polars profile:
| column | dtype | null_count | null_pct | n_unique | sample |
|---|---|---|---|---|---|
| str | str | i64 | f64 | i64 | str |
| id | Int64 | 0 | 0.0 | 169 | [1, 2, 3] |
| _index | String | 0 | 0.0 | 4 | ['euro_stoxx_50', 'euro_stoxx_… |
| symbol | String | 0 | 0.0 | 167 | ['ASML.AS', 'MC.PA', 'RMS.PA'] |
| long_name | String | 0 | 0.0 | 166 | ['ASML Holding N.V.', 'LVMH Mo… |
| short_name | String | 0 | 0.0 | 167 | ['ASML HOLDING', 'LVMH', 'HERM… |
Profile scores_daily — Pandas vs Polars describe comparison
prof_pd = profile_pd(scores_pd)
display(Markdown("*Pandas profile:*"))
display(prof_pd.head())Pandas profile:
| column | dtype | null_count | null_pct | n_unique | sample | |
|---|---|---|---|---|---|---|
| 0 | id | int64 | 0 | 0.0 | 466 | [163, 168, 174] |
| 1 | _index | object | 0 | 0.0 | 4 | ['euro_stoxx_50', 'euro_stoxx_50', 'euro_stoxx_50'] |
| 2 | symbol | object | 0 | 0.0 | 167 | ['BNP.PA', 'DTE.DE', 'IFX.DE'] |
| 3 | score_date | object | 0 | 0.0 | 3 | [datetime.date(2026, 3, 4), datetime.date(2026, 3, 4), datetime.date(2026, 3, 4)] |
| 4 | sector | object | 0 | 0.0 | 10 | ['Financial Services', 'Communication Services', 'Technology'] |
prof_pl = profile_pl(scores_pl)
display(Markdown("*Polars profile:*"))
display(prof_pl.head())Polars profile:
| column | dtype | null_count | null_pct | n_unique | sample |
|---|---|---|---|---|---|
| str | str | i64 | f64 | i64 | str |
| id | Int64 | 0 | 0.0 | 466 | [163, 168, 174] |
| _index | String | 0 | 0.0 | 4 | ['euro_stoxx_50', 'euro_stoxx_… |
| symbol | String | 0 | 0.0 | 167 | ['BNP.PA', 'DTE.DE', 'IFX.DE'] |
| score_date | Date | 0 | 0.0 | 3 | [datetime.date(2026, 3, 4), da… |
| sector | String | 0 | 0.0 | 10 | ['Financial Services', 'Commun… |
Comparison Table | Pandas vs Polars
comparison = """
| Task | Pandas | Polars |
|---|---|---|
| First N rows | `df.head(n)` | `df.head(n)` |
| Last N rows | `df.tail(n)` | `df.tail(n)` |
| Random sample | `df.sample(n)` | `df.sample(n)` |
| Transposed preview | (no built-in) | `df.glimpse()` |
| Shape | `df.shape` | `df.shape` |
| Describe (numeric) | `df.describe()` | `df.describe()` |
| Describe (all) | `df.describe(include="all")` | `df.describe()` (all by default) |
| Column dtypes | `df.dtypes` | `df.dtypes` / `df.schema` |
| Info summary | `df.info()` | (no direct equivalent) |
| Value counts | `s.value_counts()` | `s.value_counts()` |
| Unique values | `s.unique()` | `s.unique()` |
| N-unique (one col) | `s.nunique()` | `s.n_unique()` |
| N-unique (all cols) | `df.nunique()` | `df.select(pl.all().n_unique())` |
| Null count (col) | `s.isnull().sum()` | `s.null_count()` |
| Null count (all) | `df.isnull().sum()` | `df.null_count()` |
| Null percentage | `df.isnull().mean() * 100` | `pl.all().null_count() / pl.len() * 100` |
| Filter null rows | `df[df.isnull().any(axis=1)]` | `df.filter(pl.any_horizontal(pl.all().is_null()))` |
"""
display(Markdown(comparison))| Task | Pandas | Polars |
|---|---|---|
| First N rows | df.head(n) | df.head(n) |
| Last N rows | df.tail(n) | df.tail(n) |
| Random sample | df.sample(n) | df.sample(n) |
| Transposed preview | (no built-in) | df.glimpse() |
| Shape | df.shape | df.shape |
| Describe (numeric) | df.describe() | df.describe() |
| Describe (all) | df.describe(include="all") | df.describe() (all by default) |
| Column dtypes | df.dtypes | df.dtypes / df.schema |
| Info summary | df.info() | (no direct equivalent) |
| Value counts | s.value_counts() | s.value_counts() |
| Unique values | s.unique() | s.unique() |
| N-unique (one col) | s.nunique() | s.n_unique() |
| N-unique (all cols) | df.nunique() | df.select(pl.all().n_unique()) |
| Null count (col) | s.isnull().sum() | s.null_count() |
| Null count (all) | df.isnull().sum() | df.null_count() |
| Null percentage | df.isnull().mean() * 100 | pl.all().null_count() / pl.len() * 100 |
| Filter null rows | df[df.isnull().any(axis=1)] | df.filter(pl.any_horizontal(pl.all().is_null())) |
Selecting Rows & Columns
This section covers positional, label-based, and name-based row and column selection. Pandas uses .iloc[] (positional) and .loc[] (label-based). Polars uses .select(), .filter(), and pl.col() expressions.
Pandas vs Polars | Selection philosophy
Pandas provides two indexing axes:
.iloc[]for integer position and.loc[]for label-based access. Polars has no.iloc/.loc— all column selection goes through.select()with expressions, and all row filtering goes through.filter(). This eliminates theSettingWithCopyWarningand chained-indexing bugs common in Pandas.
Dataset Overview
display(Markdown("**Quick look at both datasets:**"))
display(ohlcv_pd.head(3))
display(dim_pd.drop(columns="long_business_summary").head(3))Quick look at both datasets
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| id | _index | symbol | long_name | short_name | sector | sector_key | industry | industry_key | country | city | website | exchange | full_exchange_name | exchange_timezone_name | exchange_timezone_short | currency | financial_currency | quote_type | market | range_start | price_data_start | valid_from | valid_to | is_current | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | euro_stoxx_50 | ASML.AS | ASML Holding N.V. | ASML HOLDING | Technology | technology | Semiconductor Equipment & Materials | semiconductor-equipment-materials | Netherlands | Veldhoven | https://www.asml.com | AMS | Amsterdam | Europe/Amsterdam | CET | EUR | EUR | EQUITY | nl_market | 1998-07-20 | 2021-01-01 | 2026-03-04 22:11:36.189862 | None | True |
| 1 | 2 | euro_stoxx_50 | MC.PA | LVMH Moët Hennessy - Louis Vuitton, Société Européenne | LVMH | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://www.lvmh.com | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | 2026-03-04 22:11:36.189862 | None | True |
| 2 | 3 | euro_stoxx_50 | RMS.PA | Hermès International Société en commandite par actions | HERMES INTL | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://finance.hermes.com | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | 2026-03-04 22:11:36.193940 | None | True |
Selecting Rows by Position
Single Row
# Pandas — iloc returns a Series
display(Markdown("**Pandas — single row as Series:**"))
display(ohlcv_pd.iloc[0])Pandas | single row as Series
| 0 | |
|---|---|
| id | 21160 |
| symbol | ABI.BR |
| date | 2021-01-04 |
| open | 58.15 |
| high | 58.85 |
| low | 56.78 |
| close | 57.21 |
| adj_close | 53.5761 |
| volume | 1513937 |
| dividends | 0.0 |
| stock_splits | 0.0 |
| is_filled | False |
# Polars — row() returns a tuple, slice() returns a 1-row DataFrame
display(Markdown("**Polars — single row as tuple:**"))
print(ohlcv_pl.row(0))
display(Markdown("**Polars — single row as DataFrame:**"))
display(ohlcv_pl.slice(0, 1))Polars | single row as tuple
(21160, ‘ABI.BR’, datetime.date(2021, 1, 4), 58.15, 58.85, 56.78, 57.21, 53.5761, 1513937, 0.0, 0.0, False)
Polars | single row as DataFrame
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
Multiple Rows by Position
# Pandas — pass a list of positions
display(Markdown("**Pandas — rows at positions 0, 10, 100:**"))
display(ohlcv_pd.iloc[[0, 10, 100]])Pandas | rows at positions 0, 10, 100
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 10 | 21170 | ABI.BR | 2021-01-18 | 56.25 | 57.30 | 56.20 | 57.08 | 53.4544 | 730298 | 0.0 | 0.0 | False |
| 100 | 21260 | ABI.BR | 2021-05-26 | 61.99 | 62.39 | 61.83 | 62.12 | 58.6701 | 940186 | 0.0 | 0.0 | False |
# Polars — bracket indexing with a list
display(Markdown("**Polars — rows at positions 0, 10, 100:**"))
display(ohlcv_pl[[0, 10, 100]])Polars | rows at positions 0, 10, 100
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21170 | ABI.BR | 2021-01-18 | 56.25 | 57.3 | 56.2 | 57.08 | 53.4544 | 730298 | 0.0 | 0.0 | false |
| 21260 | ABI.BR | 2021-05-26 | 61.99 | 62.39 | 61.83 | 62.12 | 58.6701 | 940186 | 0.0 | 0.0 | false |
Row Slicing
# Pandas — standard Python slicing (start:stop)
display(Markdown("**Pandas — rows 10 to 14:**"))
display(ohlcv_pd.iloc[10:15])Pandas | rows 10 to 14
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10 | 21170 | ABI.BR | 2021-01-18 | 56.25 | 57.30 | 56.20 | 57.08 | 53.4544 | 730298 | 0.0 | 0.0 | False |
| 11 | 21171 | ABI.BR | 2021-01-19 | 57.10 | 57.26 | 56.26 | 56.35 | 52.7707 | 1116570 | 0.0 | 0.0 | False |
| 12 | 21172 | ABI.BR | 2021-01-20 | 56.35 | 56.77 | 56.00 | 56.24 | 52.6677 | 1226516 | 0.0 | 0.0 | False |
| 13 | 21173 | ABI.BR | 2021-01-21 | 56.20 | 56.55 | 55.31 | 55.31 | 51.7968 | 1404283 | 0.0 | 0.0 | False |
| 14 | 21174 | ABI.BR | 2021-01-22 | 55.28 | 55.28 | 54.12 | 54.78 | 51.3005 | 1557287 | 0.0 | 0.0 | False |
# Polars — slice(offset, length)
display(Markdown("**Polars — rows 10 to 14:**"))
display(ohlcv_pl.slice(10, 5))Polars | rows 10 to 14
| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21170 | ABI.BR | 2021-01-18 | 56.25 | 57.3 | 56.2 | 57.08 | 53.4544 | 730298 | 0.0 | 0.0 | false |
| 21171 | ABI.BR | 2021-01-19 | 57.1 | 57.26 | 56.26 | 56.35 | 52.7707 | 1116570 | 0.0 | 0.0 | false |
| 21172 | ABI.BR | 2021-01-20 | 56.35 | 56.77 | 56.0 | 56.24 | 52.6677 | 1226516 | 0.0 | 0.0 | false |
| 21173 | ABI.BR | 2021-01-21 | 56.2 | 56.55 | 55.31 | 55.31 | 51.7968 | 1404283 | 0.0 | 0.0 | false |
| 21174 | ABI.BR | 2021-01-22 | 55.28 | 55.28 | 54.12 | 54.78 | 51.3005 | 1557287 | 0.0 | 0.0 | false |
Row by Label
Pandas DataFrames have a row index that supports label-based access via loc.
Polars has no row index — use filter() instead.
# Pandas — loc with label-based indexing
# Set 'symbol' as index to demonstrate label access
dim_indexed = dim_pd.drop(columns="long_business_summary").set_index("symbol")
display(Markdown("**Pandas — loc with label index:**"))
display(dim_indexed.loc[["ASML.AS", "SAP.DE"]])Pandas | loc with label index
| id | _index | long_name | short_name | sector | sector_key | industry | industry_key | country | city | website | exchange | full_exchange_name | exchange_timezone_name | exchange_timezone_short | currency | financial_currency | quote_type | market | range_start | price_data_start | valid_from | valid_to | is_current | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| symbol | ||||||||||||||||||||||||
| ASML.AS | 1 | euro_stoxx_50 | ASML Holding N.V. | ASML HOLDING | Technology | technology | Semiconductor Equipment & Materials | semiconductor-equipment-materials | Netherlands | Veldhoven | https://www.asml.com | AMS | Amsterdam | Europe/Amsterdam | CET | EUR | EUR | EQUITY | nl_market | 1998-07-20 | 2021-01-01 | 2026-03-04 22:11:36.189862 | None | True |
| SAP.DE | 5 | euro_stoxx_50 | SAP SE | SAP SE | Technology | technology | Software - Application | software-application | Germany | Walldorf | https://www.sap.com | GER | XETRA | Europe/Berlin | CET | EUR | EUR | EQUITY | de_market | 1998-04-09 | 2021-01-01 | 2026-03-04 22:11:36.193940 | None | True |
# Polars — no index, use filter instead
display(Markdown("**Polars — filter as label equivalent:**"))
display(dim_pl.filter(pl.col("symbol").is_in(["ASML.AS", "SAP.DE"])).drop("long_business_summary"))Polars | filter as label equivalent
| id | _index | symbol | long_name | short_name | sector | sector_key | industry | industry_key | country | city | website | exchange | full_exchange_name | exchange_timezone_name | exchange_timezone_short | currency | financial_currency | quote_type | market | range_start | price_data_start | valid_from | valid_to | is_current |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | date | date | datetime[ns] | null | bool |
| 1 | euro_stoxx_50 | ASML.AS | ASML Holding N.V. | ASML HOLDING | Technology | technology | Semiconductor Equipment & Mate… | semiconductor-equipment-materi… | Netherlands | Veldhoven | https://www.asml.com | AMS | Amsterdam | Europe/Amsterdam | CET | EUR | EUR | EQUITY | nl_market | 1998-07-20 | 2021-01-01 | 2026-03-04 22:11:36.189862 | null | true |
| 5 | euro_stoxx_50 | SAP.DE | SAP SE | SAP SE | Technology | technology | Software - Application | software-application | Germany | Walldorf | https://www.sap.com | GER | XETRA | Europe/Berlin | CET | EUR | EUR | EQUITY | de_market | 1998-04-09 | 2021-01-01 | 2026-03-04 22:11:36.193940 | null | true |
Selecting Rows and Columns Together
By Position
# Pandas — iloc[rows, cols]
display(Markdown("**Pandas — rows 0-4, columns 2-5:**"))
display(ohlcv_pd.iloc[:5, 2:6])Pandas | rows 0-4, columns 2-5
| date | open | high | low | |
|---|---|---|---|---|
| 0 | 2021-01-04 | 58.15 | 58.85 | 56.78 |
| 1 | 2021-01-05 | 56.90 | 57.98 | 56.75 |
| 2 | 2021-01-06 | 57.96 | 58.94 | 57.39 |
| 3 | 2021-01-07 | 58.68 | 58.86 | 57.88 |
| 4 | 2021-01-08 | 58.16 | 58.40 | 57.43 |
# Polars — slice + select by column names (no positional column indexing)
display(Markdown("**Polars — rows 0-4, columns date through close:**"))
cols = ohlcv_pl.columns[2:6]
display(ohlcv_pl.slice(0, 5).select(cols))Polars | rows 0-4, columns date through close
| date | open | high | low |
|---|---|---|---|
| date | f64 | f64 | f64 |
| 2021-01-04 | 58.15 | 58.85 | 56.78 |
| 2021-01-05 | 56.9 | 57.98 | 56.75 |
| 2021-01-06 | 57.96 | 58.94 | 57.39 |
| 2021-01-07 | 58.68 | 58.86 | 57.88 |
| 2021-01-08 | 58.16 | 58.4 | 57.43 |
By Name and Condition
# Pandas — loc with condition + column names
display(Markdown("**Pandas — ASML rows, selected columns:**"))
display(ohlcv_pd.loc[ohlcv_pd["symbol"] == "ASML.AS", ["date", "close", "volume"]].head())Pandas | ASML rows, selected columns
| date | close | volume | |
|---|---|---|---|
| 10634 | 2021-01-04 | 406.25 | 789502 |
| 10635 | 2021-01-05 | 406.90 | 798787 |
| 10636 | 2021-01-06 | 402.85 | 875711 |
| 10637 | 2021-01-07 | 403.90 | 874780 |
| 10638 | 2021-01-08 | 416.05 | 975243 |
# Polars — filter + select
display(Markdown("**Polars — ASML rows, selected columns:**"))
display(
ohlcv_pl
.filter(pl.col("symbol") == "ASML.AS")
.select("date", "close", "volume")
.head()
)Polars | ASML rows, selected columns
| date | close | volume |
|---|---|---|
| date | f64 | i64 |
| 2021-01-04 | 406.25 | 789502 |
| 2021-01-05 | 406.9 | 798787 |
| 2021-01-06 | 402.85 | 875711 |
| 2021-01-07 | 403.9 | 874780 |
| 2021-01-08 | 416.05 | 975243 |
Practical Subset from Dimension Table
# Pandas — first 5 stocks, just name and sector
display(Markdown("**Pandas:**"))
display(dim_pd.iloc[:5][["symbol", "long_name", "sector", "country"]])Pandas | first 5 stocks — symbol, name, sector, country
| symbol | long_name | sector | country | |
|---|---|---|---|---|
| 0 | ASML.AS | ASML Holding N.V. | Technology | Netherlands |
| 1 | MC.PA | LVMH Moët Hennessy - Louis Vuitton, Société Européenne | Consumer Cyclical | France |
| 2 | RMS.PA | Hermès International Société en commandite par actions | Consumer Cyclical | France |
| 3 | OR.PA | L'Oréal S.A. | Consumer Defensive | France |
| 4 | SAP.DE | SAP SE | Technology | Germany |
# Polars — first 5 stocks, just name and sector
display(Markdown("**Polars:**"))
display(dim_pl.slice(0, 5).select("symbol", "long_name", "sector", "country"))Polars | first 5 stocks — symbol, name, sector, country
| symbol | long_name | sector | country |
|---|---|---|---|
| str | str | str | str |
| ASML.AS | ASML Holding N.V. | Technology | Netherlands |
| MC.PA | LVMH Moët Hennessy - Louis Vui… | Consumer Cyclical | France |
| RMS.PA | Hermès International Société e… | Consumer Cyclical | France |
| OR.PA | L'Oréal S.A. | Consumer Defensive | France |
| SAP.DE | SAP SE | Technology | Germany |
Single Column Selection
Pandas | bracket and dot notation
Selects the close column from ohlcv_pd using bracket notation and dot attribute access, both returning the first 5 rows as an identical Series — demonstrating that df["col"] and df.col are interchangeable for column retrieval.
display(ohlcv_pd["close"].head())| close | |
|---|---|
| 0 | 57.21 |
| 1 | 57.18 |
| 2 | 58.77 |
| 3 | 58.40 |
| 4 | 57.86 |
display(ohlcv_pd.close.head())| close | |
|---|---|
| 0 | 57.21 |
| 1 | 57.18 |
| 2 | 58.77 |
| 3 | 58.40 |
| 4 | 57.86 |
Polars | select() and pl.col()
Selects symbol, date, and close from ohlcv_pl using string shorthand in select(), then repeats with explicit pl.col() expressions — confirming that bare strings and pl.col() are interchangeable column references, both returning a 5-row, 2–3 column result.
display(ohlcv_pl.select("symbol", "date", "close").head())| symbol | date | close |
|---|---|---|
| str | date | f64 |
| ABI.BR | 2021-01-04 | 57.21 |
| ABI.BR | 2021-01-05 | 57.18 |
| ABI.BR | 2021-01-06 | 58.77 |
| ABI.BR | 2021-01-07 | 58.4 |
| ABI.BR | 2021-01-08 | 57.86 |
display(ohlcv_pl.select(pl.col("symbol"), pl.col("close")).head())| symbol | close |
|---|---|
| str | f64 |
| ABI.BR | 57.21 |
| ABI.BR | 57.18 |
| ABI.BR | 58.77 |
| ABI.BR | 58.4 |
| ABI.BR | 57.86 |
Multiple Column Selection
Pandas | list, loc
Selects three columns from ohlcv_pd using a double-bracket list — the standard Pandas idiom for returning a DataFrame (not a Series) with a named column subset — then demonstrates loc[:, [...]] and loc[:, "open":"close"] for label-based multi-column access.
display(ohlcv_pd[["symbol", "date", "close"]].head())| symbol | date | close | |
|---|---|---|---|
| 0 | ABI.BR | 2021-01-04 | 57.21 |
| 1 | ABI.BR | 2021-01-05 | 57.18 |
| 2 | ABI.BR | 2021-01-06 | 58.77 |
| 3 | ABI.BR | 2021-01-07 | 58.40 |
| 4 | ABI.BR | 2021-01-08 | 57.86 |
display(Markdown("**Select specific columns with `loc`:**"))
display(ohlcv_pd.loc[:, ["symbol", "open", "close"]].head())Select specific columns with loc
| symbol | open | close | |
|---|---|---|---|
| 0 | ABI.BR | 58.15 | 57.21 |
| 1 | ABI.BR | 56.90 | 57.18 |
| 2 | ABI.BR | 57.96 | 58.77 |
| 3 | ABI.BR | 58.68 | 58.40 |
| 4 | ABI.BR | 58.16 | 57.86 |
display(Markdown("**Slice columns with `loc` (label range):**"))
display(ohlcv_pd.loc[:, "open":"close"].head())Slice columns with loc (label range)
| open | high | low | close | |
|---|---|---|---|---|
| 0 | 58.15 | 58.85 | 56.78 | 57.21 |
| 1 | 56.90 | 57.98 | 56.75 | 57.18 |
| 2 | 57.96 | 58.94 | 57.39 | 58.77 |
| 3 | 58.68 | 58.86 | 57.88 | 58.40 |
| 4 | 58.16 | 58.40 | 57.43 | 57.86 |
Polars | pl.col() with a list
Passes a Python list of column names to pl.col() inside select(), returning the five OHLCV price columns — demonstrating that pl.col(["a", "b", ...]) is a concise alternative to pl.col("a"), pl.col("b"), ... for multi-column selection.
display(ohlcv_pl.select(pl.col(["symbol", "open", "high", "low", "close"])).head())| symbol | open | high | low | close |
|---|---|---|---|---|
| str | f64 | f64 | f64 | f64 |
| ABI.BR | 58.15 | 58.85 | 56.78 | 57.21 |
| ABI.BR | 56.9 | 57.98 | 56.75 | 57.18 |
| ABI.BR | 57.96 | 58.94 | 57.39 | 58.77 |
| ABI.BR | 58.68 | 58.86 | 57.88 | 58.4 |
| ABI.BR | 58.16 | 58.4 | 57.43 | 57.86 |
Column Selection by Position
Pandas | iloc
Uses iloc[:, :3] to return the first three columns (id, symbol, date) of ohlcv_pd by position, then iloc[:, [0, 2, 4]] to retrieve non-contiguous columns id, date, and high — both operations across all rows.
display(Markdown("**First three columns by position:**"))
display(ohlcv_pd.iloc[:, :3].head())First three columns by position
| id | symbol | date | |
|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 |
| 1 | 21161 | ABI.BR | 2021-01-05 |
| 2 | 21162 | ABI.BR | 2021-01-06 |
| 3 | 21163 | ABI.BR | 2021-01-07 |
| 4 | 21164 | ABI.BR | 2021-01-08 |
display(Markdown("**Columns at positions 0, 2, 4:**"))
display(ohlcv_pd.iloc[:, [0, 2, 4]].head())Columns at positions 0, 2, 4
| id | date | high | |
|---|---|---|---|
| 0 | 21160 | 2021-01-04 | 58.85 |
| 1 | 21161 | 2021-01-05 | 57.98 |
| 2 | 21162 | 2021-01-06 | 58.94 |
| 3 | 21163 | 2021-01-07 | 58.86 |
| 4 | 21164 | 2021-01-08 | 58.40 |
Polars | index into columns list
Slices ohlcv_pl.columns (a Python list) to get the first three column names, then passes them to select() — and repeats with a list comprehension for non-contiguous positions 0, 2, 4 — since Polars has no iloc-style positional column indexer.
# Polars has no positional column indexing — slice the columns list
display(Markdown("**First three columns by position:**"))
display(ohlcv_pl.select(ohlcv_pl.columns[:3]).head())
display(Markdown("**Columns at positions 0, 2, 4:**"))
display(ohlcv_pl.select([ohlcv_pl.columns[i] for i in [0, 2, 4]]).head())First three columns by position
| id | symbol | date |
|---|---|---|
| i64 | str | date |
| 21160 | ABI.BR | 2021-01-04 |
| 21161 | ABI.BR | 2021-01-05 |
| 21162 | ABI.BR | 2021-01-06 |
| 21163 | ABI.BR | 2021-01-07 |
| 21164 | ABI.BR | 2021-01-08 |
Columns at positions 0, 2, 4
| id | date | high |
|---|---|---|
| i64 | date | f64 |
| 21160 | 2021-01-04 | 58.85 |
| 21161 | 2021-01-05 | 57.98 |
| 21162 | 2021-01-06 | 58.94 |
| 21163 | 2021-01-07 | 58.86 |
| 21164 | 2021-01-08 | 58.4 |
Select All / Exclude Columns
Pandas selects all columns by default; exclusion uses drop() (covered later).
Polars provides pl.all() and pl.exclude() as expression-level selectors.
Polars | pl.all() and pl.exclude()
Uses pl.all() inside select() to pass through all 12 columns of ohlcv_pl unmodified, then pl.exclude("volume") to drop a single column and pl.exclude(["volume", "symbol"]) to drop two — demonstrating expression-level exclusion without listing every kept column.
display(ohlcv_pl.select(pl.all()).head(3))| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
display(Markdown("**All columns except volume:**"))
display(ohlcv_pl.select(pl.exclude("volume")).head())All columns except volume
| id | symbol | date | open | high | low | close | adj_close | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 0.0 | 0.0 | false |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 0.0 | 0.0 | false |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 0.0 | 0.0 | false |
display(Markdown("**Exclude multiple columns:**"))
display(ohlcv_pl.select(pl.exclude(["volume", "symbol"])).head())Exclude multiple columns
| id | date | open | high | low | close | adj_close | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|
| i64 | date | f64 | f64 | f64 | f64 | f64 | f64 | f64 | bool |
| 21160 | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 0.0 | 0.0 | false |
| 21161 | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 0.0 | 0.0 | false |
| 21162 | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 0.0 | 0.0 | false |
| 21163 | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 0.0 | 0.0 | false |
| 21164 | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 0.0 | 0.0 | false |
Column Selection by Dtype
Pandas | select_dtypes()
Passes include="number" to select_dtypes() on ohlcv_pd, returning the 9 numeric columns (id, open, high, low, close, adj_close, volume, dividends, stock_splits) and filtering out symbol, date, and is_filled — then repeats with include="object" to isolate the two string columns.
display(Markdown("**Numeric columns only:**"))
display(ohlcv_pd.select_dtypes(include="number").head())Numeric columns only
| id | open | high | low | close | adj_close | volume | dividends | stock_splits | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 |
| 1 | 21161 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 |
| 2 | 21162 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 |
| 3 | 21163 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 |
| 4 | 21164 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 |
display(Markdown("**Object / string columns only:**"))
display(ohlcv_pd.select_dtypes(include="object").head())Object / string columns only
| symbol | date | |
|---|---|---|
| 0 | ABI.BR | 2021-01-04 |
| 1 | ABI.BR | 2021-01-05 |
| 2 | ABI.BR | 2021-01-06 |
| 3 | ABI.BR | 2021-01-07 |
| 4 | ABI.BR | 2021-01-08 |
Polars | polars.selectors
Uses cs.numeric(), cs.string(), and cs.temporal() on ohlcv_pl to select 9 numeric columns, 1 string column (symbol), and 1 temporal column (date) respectively — then demonstrates cs.by_dtype(pl.Float64) and cs.by_dtype(pl.Int64) for single-dtype filtering.
display(ohlcv_pl.select(cs.numeric()).head())| id | open | high | low | close | adj_close | volume | dividends | stock_splits |
|---|---|---|---|---|---|---|---|---|
| i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 |
| 21160 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 |
| 21161 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 |
| 21162 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 |
| 21163 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 |
| 21164 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 |
display(ohlcv_pl.select(cs.string()).head())| symbol |
|---|
| str |
| ABI.BR |
| ABI.BR |
| ABI.BR |
| ABI.BR |
| ABI.BR |
display(ohlcv_pl.select(cs.temporal()).head())| date |
|---|
| date |
| 2021-01-04 |
| 2021-01-05 |
| 2021-01-06 |
| 2021-01-07 |
| 2021-01-08 |
display(Markdown("**Float64 columns only:**"))
display(ohlcv_pl.select(cs.by_dtype(pl.Float64)).head())Float64 columns only
| open | high | low | close | adj_close | dividends | stock_splits |
|---|---|---|---|---|---|---|
| f64 | f64 | f64 | f64 | f64 | f64 | f64 |
| 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 0.0 | 0.0 |
| 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 0.0 | 0.0 |
| 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 0.0 | 0.0 |
| 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 0.0 | 0.0 |
| 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 0.0 | 0.0 |
display(Markdown("**Int64 columns only:**"))
display(ohlcv_pl.select(cs.by_dtype(pl.Int64)).head())Int64 columns only
| id | volume |
|---|---|
| i64 | i64 |
| 21160 | 1513937 |
| 21161 | 1382722 |
| 21162 | 1370204 |
| 21163 | 1469911 |
| 21164 | 1428681 |
Column Selection by Pattern / Regex
Pandas | filter(regex=…)
Applies df.filter(regex="o") to ohlcv_pd, returning the 7 columns whose names contain the letter “o” (symbol, open, low, close, adj_close, volume, stock_splits) — then demonstrates anchored patterns: ^c for names starting with “c” and e for names containing “e”.
display(Markdown("**Columns matching regex (contains 'o'):**"))
display(ohlcv_pd.filter(regex="o").head())Columns matching regex (contains ‘o’)
| symbol | open | low | close | adj_close | volume | stock_splits | |
|---|---|---|---|---|---|---|---|
| 0 | ABI.BR | 58.15 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 |
| 1 | ABI.BR | 56.90 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 |
| 2 | ABI.BR | 57.96 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 |
| 3 | ABI.BR | 58.68 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 |
| 4 | ABI.BR | 58.16 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 |
display(Markdown("**Columns starting with 'c':**"))
display(ohlcv_pd.filter(regex="^c").head())Columns starting with ‘c’
| close | |
|---|---|
| 0 | 57.21 |
| 1 | 57.18 |
| 2 | 58.77 |
| 3 | 58.40 |
| 4 | 57.86 |
display(Markdown("**Columns whose name contains 'e':**"))
display(ohlcv_pd.filter(regex="e").head())Columns whose name contains ‘e’
| date | open | close | adj_close | volume | dividends | is_filled | |
|---|---|---|---|---|---|---|---|
| 0 | 2021-01-04 | 58.15 | 57.21 | 53.5761 | 1513937 | 0.0 | False |
| 1 | 2021-01-05 | 56.90 | 57.18 | 53.5480 | 1382722 | 0.0 | False |
| 2 | 2021-01-06 | 57.96 | 58.77 | 55.0370 | 1370204 | 0.0 | False |
| 3 | 2021-01-07 | 58.68 | 58.40 | 54.6905 | 1469911 | 0.0 | False |
| 4 | 2021-01-08 | 58.16 | 57.86 | 54.1848 | 1428681 | 0.0 | False |
Polars | pl.col(“^regex$”) and cs.by_name()
Uses cs.by_name("open", "close") to select two columns by exact name, then demonstrates Polars regex column selection with pl.col("^(c|o).*$") for names starting with “c” or “o” and pl.col("^.*e$") for names ending with “e” — all anchored with ^...$ as Polars requires.
display(ohlcv_pl.select(cs.by_name("open", "close")).head())| open | close |
|---|---|
| f64 | f64 |
| 58.15 | 57.21 |
| 56.9 | 57.18 |
| 57.96 | 58.77 |
| 58.68 | 58.4 |
| 58.16 | 57.86 |
display(Markdown("**Columns whose name starts with 'c' or 'o' (regex):**"))
display(ohlcv_pl.select(pl.col("^(c|o).*$")).head())Columns whose name starts with ‘c’ or ‘o’ (regex)
| open | close |
|---|---|
| f64 | f64 |
| 58.15 | 57.21 |
| 56.9 | 57.18 |
| 57.96 | 58.77 |
| 58.68 | 58.4 |
| 58.16 | 57.86 |
display(Markdown("**Columns ending with 'e':**"))
display(ohlcv_pl.select(pl.col("^.*e$")).head())Columns ending with ‘e’
| date | close | adj_close | volume |
|---|---|---|---|
| date | f64 | f64 | i64 |
| 2021-01-04 | 57.21 | 53.5761 | 1513937 |
| 2021-01-05 | 57.18 | 53.548 | 1382722 |
| 2021-01-06 | 58.77 | 55.037 | 1370204 |
| 2021-01-07 | 58.4 | 54.6905 | 1469911 |
| 2021-01-08 | 57.86 | 54.1848 | 1428681 |
Combining Selectors (Polars)
polars.selectors supports set operations: | (union), - (difference), ~ (invert).
display(Markdown("**Numeric BUT NOT Int64:**"))
display(ohlcv_pl.select(cs.numeric() - cs.by_dtype(pl.Int64)).head())Numeric BUT NOT Int64
| open | high | low | close | adj_close | dividends | stock_splits |
|---|---|---|---|---|---|---|
| f64 | f64 | f64 | f64 | f64 | f64 | f64 |
| 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 0.0 | 0.0 |
| 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 0.0 | 0.0 |
| 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 0.0 | 0.0 |
| 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 0.0 | 0.0 |
| 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 0.0 | 0.0 |
display(Markdown("**Numeric OR temporal:**"))
display(ohlcv_pl.select(cs.numeric() | cs.temporal()).head())Numeric OR temporal
| id | date | open | high | low | close | adj_close | volume | dividends | stock_splits |
|---|---|---|---|---|---|---|---|---|---|
| i64 | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 |
| 21160 | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 |
| 21161 | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 |
| 21162 | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 |
| 21163 | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 |
| 21164 | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 |
display(Markdown("**Invert a selector (everything NOT numeric):**"))
display(ohlcv_pl.select(~cs.numeric()).head())Invert a selector (everything NOT numeric)
| symbol | date | is_filled |
|---|---|---|
| str | date | bool |
| ABI.BR | 2021-01-04 | false |
| ABI.BR | 2021-01-05 | false |
| ABI.BR | 2021-01-06 | false |
| ABI.BR | 2021-01-07 | false |
| ABI.BR | 2021-01-08 | false |
Renaming Columns
Pandas | rename()
- Rename: Rename columns.
Renames open to Open and close to Close in ohlcv_pd using a columns dictionary — the result confirms both columns are capitalised while the remaining 10 columns are unchanged.
display(
ohlcv_pd.rename(columns={"open": "Open", "close": "Close"}).head(3)
)| id | symbol | date | Open | high | low | Close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
Polars | rename()
- Rename: Rename columns.
Applies the same two-column rename as the Pandas example using a plain dict (no columns= keyword) — confirms API parity while highlighting that Polars rename() takes the mapping as the first positional argument.
display(
ohlcv_pl.rename({"open": "Open", "close": "Close"}).head(3)
)| id | symbol | date | Open | high | low | Close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
Polars | alias() inside select()
- pl.col: Reference a column by name. The foundation of all Polars expressions.
- Alias: Give an expression result a column name (Polars).
Selects symbol and date unchanged, renames close to closing_price and volume to vol using .alias() per expression — reducing the result from 12 to 4 named columns without a separate rename step.
display(
ohlcv_pl.select(
pl.col("symbol"),
pl.col("date"),
pl.col("close").alias("closing_price"),
pl.col("volume").alias("vol"),
).head()
)| symbol | date | closing_price | vol |
|---|---|---|---|
| str | date | f64 | i64 |
| ABI.BR | 2021-01-04 | 57.21 | 1513937 |
| ABI.BR | 2021-01-05 | 57.18 | 1382722 |
| ABI.BR | 2021-01-06 | 58.77 | 1370204 |
| ABI.BR | 2021-01-07 | 58.4 | 1469911 |
| ABI.BR | 2021-01-08 | 57.86 | 1428681 |
Polars | name.prefix() / name.suffix()
- Selector: Numeric: Select all numeric columns (Polars selectors module).
Adds a num_ prefix to all 9 numeric column names using cs.numeric().name.prefix(), then demonstrates pl.all().name.suffix("_raw") to append _raw to all 12 column names — showing both approaches to bulk column renaming via name modifiers.
display(Markdown("**Add prefix to numeric columns:**"))
display(
ohlcv_pl.select(cs.numeric().name.prefix("num_")).head(3)
)Add prefix to numeric columns
| num_id | num_open | num_high | num_low | num_close | num_adj_close | num_volume | num_dividends | num_stock_splits |
|---|---|---|---|---|---|---|---|---|
| i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 |
| 21160 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 |
| 21161 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 |
| 21162 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 |
display(Markdown("**Add suffix to all columns:**"))
display(
ohlcv_pl.select(pl.all().name.suffix("_raw")).head(3)
)Add suffix to all columns
| id_raw | symbol_raw | date_raw | open_raw | high_raw | low_raw | close_raw | adj_close_raw | volume_raw | dividends_raw | stock_splits_raw | is_filled_raw |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
Reordering Columns
Pandas | explicit list
Reorders ohlcv_pd to a 7-column subset with date and symbol first, followed by OHLCV price columns — using a manually defined list as the column index to achieve reordering and column subsetting in one bracket operation.
new_order = ["date", "symbol", "close", "open", "high", "low", "volume"]
display(ohlcv_pd[new_order].head(3))| date | symbol | close | open | high | low | volume | |
|---|---|---|---|---|---|---|---|
| 0 | 2021-01-04 | ABI.BR | 57.21 | 58.15 | 58.85 | 56.78 | 1513937 |
| 1 | 2021-01-05 | ABI.BR | 57.18 | 56.90 | 57.98 | 56.75 | 1382722 |
| 2 | 2021-01-06 | ABI.BR | 58.77 | 57.96 | 58.94 | 57.39 | 1370204 |
Polars | select() reorders
Reorders ohlcv_pl to the same 7-column subset as the Pandas example by passing the desired column order directly to select() — demonstrating that Polars select() naturally reorders and subsets in one step.
display(
ohlcv_pl.select("date", "symbol", "close", "open", "high", "low", "volume").head(3)
)| date | symbol | close | open | high | low | volume |
|---|---|---|---|---|---|---|
| date | str | f64 | f64 | f64 | f64 | i64 |
| 2021-01-04 | ABI.BR | 57.21 | 58.15 | 58.85 | 56.78 | 1513937 |
| 2021-01-05 | ABI.BR | 57.18 | 56.9 | 57.98 | 56.75 | 1382722 |
| 2021-01-06 | ABI.BR | 58.77 | 57.96 | 58.94 | 57.39 | 1370204 |
Polars | move specific columns to front
Moves date and symbol to the front of ohlcv_pl while preserving all 12 columns by splitting the column list into front and rest, then concatenating them as the select() argument.
front = ["date", "symbol"]
rest = [c for c in ohlcv_pl.columns if c not in front]
display(ohlcv_pl.select(front + rest).head(3))| date | symbol | id | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| date | str | i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 2021-01-04 | ABI.BR | 21160 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 2021-01-05 | ABI.BR | 21161 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 2021-01-06 | ABI.BR | 21162 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
Dropping Columns
Pandas | drop()
Removes the volume column from ohlcv_pd using drop(columns=[...]), returning an 11-column DataFrame, then drops both volume and open to produce a 10-column result.
display(ohlcv_pd.drop(columns=["volume"]).head(3))| id | symbol | date | open | high | low | close | adj_close | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 0.0 | 0.0 | False |
display(Markdown("**Drop multiple columns:**"))
display(ohlcv_pd.drop(columns=["volume", "open"]).head(3))Drop multiple columns
| id | symbol | date | high | low | close | adj_close | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.85 | 56.78 | 57.21 | 53.5761 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 57.98 | 56.75 | 57.18 | 53.5480 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 58.94 | 57.39 | 58.77 | 55.0370 | 0.0 | 0.0 | False |
Polars | drop()
Removes volume from ohlcv_pl using drop() with a bare string (no list required for a single column), returning an 11-column DataFrame — then drops both volume and open to confirm drop("a", "b") variadic syntax.
display(ohlcv_pl.drop("volume").head(3))| id | symbol | date | open | high | low | close | adj_close | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 0.0 | 0.0 | false |
display(Markdown("**Drop multiple columns:**"))
display(ohlcv_pl.drop("volume", "open").head(3))Drop multiple columns
| id | symbol | date | high | low | close | adj_close | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.85 | 56.78 | 57.21 | 53.5761 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 57.98 | 56.75 | 57.18 | 53.548 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 58.94 | 57.39 | 58.77 | 55.037 | 0.0 | 0.0 | false |
Selection Patterns on the Dimension Table
display(Markdown("**Dimension table columns:**"))
print("Pandas:\n", dim_pd.columns.tolist())
print("\nPolars:\n", dim_pl.columns)Dimension table columns
Pandas: [‘id’, ‘_index’, ‘symbol’, ‘long_name’, ‘short_name’, ‘sector’, ‘sector_key’, ‘industry’, ‘industry_key’, ‘country’, ‘city’, ‘website’, ‘long_business_summary’, ‘exchange’, ‘full_exchange_name’, ‘exchange_timezone_name’, ‘exchange_timezone_short’, ‘currency’, ‘financial_currency’, ‘quote_type’, ‘market’, ‘range_start’, ‘price_data_start’, ‘valid_from’, ‘valid_to’, ‘is_current’]
Polars: [‘id’, ‘_index’, ‘symbol’, ‘long_name’, ‘short_name’, ‘sector’, ‘sector_key’, ‘industry’, ‘industry_key’, ‘country’, ‘city’, ‘website’, ‘long_business_summary’, ‘exchange’, ‘full_exchange_name’, ‘exchange_timezone_name’, ‘exchange_timezone_short’, ‘currency’, ‘financial_currency’, ‘quote_type’, ‘market’, ‘range_start’, ‘price_data_start’, ‘valid_from’, ‘valid_to’, ‘is_current’]
display(Markdown("**Pandas — select string columns:**"))
display(dim_pd.select_dtypes(include="object").drop(columns="long_business_summary").head())Pandas | select string columns
| _index | symbol | long_name | short_name | sector | sector_key | industry | industry_key | country | city | website | exchange | full_exchange_name | exchange_timezone_name | exchange_timezone_short | currency | financial_currency | quote_type | market | range_start | price_data_start | valid_to | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | euro_stoxx_50 | ASML.AS | ASML Holding N.V. | ASML HOLDING | Technology | technology | Semiconductor Equipment & Materials | semiconductor-equipment-materials | Netherlands | Veldhoven | https://www.asml.com | AMS | Amsterdam | Europe/Amsterdam | CET | EUR | EUR | EQUITY | nl_market | 1998-07-20 | 2021-01-01 | None |
| 1 | euro_stoxx_50 | MC.PA | LVMH Moët Hennessy - Louis Vuitton, Société Européenne | LVMH | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://www.lvmh.com | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | None |
| 2 | euro_stoxx_50 | RMS.PA | Hermès International Société en commandite par actions | HERMES INTL | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://finance.hermes.com | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | None |
| 3 | euro_stoxx_50 | OR.PA | L'Oréal S.A. | L'OREAL | Consumer Defensive | consumer-defensive | Household & Personal Products | household-personal-products | France | Clichy | https://www.loreal.com | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market | 2000-01-03 | 2021-01-01 | None |
| 4 | euro_stoxx_50 | SAP.DE | SAP SE | SAP SE | Technology | technology | Software - Application | software-application | Germany | Walldorf | https://www.sap.com | GER | XETRA | Europe/Berlin | CET | EUR | EUR | EQUITY | de_market | 1998-04-09 | 2021-01-01 | None |
display(Markdown("**Polars — select string columns with cs.string():**"))
display(dim_pl.select(cs.string()).head())Polars | select string columns with cs.string()
| _index | symbol | long_name | short_name | sector | sector_key | industry | industry_key | country | city | website | long_business_summary | exchange | full_exchange_name | exchange_timezone_name | exchange_timezone_short | currency | financial_currency | quote_type | market |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str | str |
| euro_stoxx_50 | ASML.AS | ASML Holding N.V. | ASML HOLDING | Technology | technology | Semiconductor Equipment & Mate… | semiconductor-equipment-materi… | Netherlands | Veldhoven | https://www.asml.com | ASML Holding N.V. provides lit… | AMS | Amsterdam | Europe/Amsterdam | CET | EUR | EUR | EQUITY | nl_market |
| euro_stoxx_50 | MC.PA | LVMH Moët Hennessy - Louis Vui… | LVMH | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://www.lvmh.com | LVMH Moët Hennessy - Louis Vui… | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market |
| euro_stoxx_50 | RMS.PA | Hermès International Société e… | HERMES INTL | Consumer Cyclical | consumer-cyclical | Luxury Goods | luxury-goods | France | Paris | https://finance.hermes.com | Hermès International Société e… | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market |
| euro_stoxx_50 | OR.PA | L'Oréal S.A. | L'OREAL | Consumer Defensive | consumer-defensive | Household & Personal Products | household-personal-products | France | Clichy | https://www.loreal.com | L'Oréal S.A., through its subs… | PAR | Paris | Europe/Paris | CET | EUR | EUR | EQUITY | fr_market |
| euro_stoxx_50 | SAP.DE | SAP SE | SAP SE | Technology | technology | Software - Application | software-application | Germany | Walldorf | https://www.sap.com | SAP SE, together with its subs… | GER | XETRA | Europe/Berlin | CET | EUR | EUR | EQUITY | de_market |
display(Markdown("**Polars — select with cs.matches() regex:**"))
display(dim_pl.select(cs.matches(".*name.*|.*id.*")).head())Polars | select with cs.matches() regex
| id | long_name | short_name | full_exchange_name | exchange_timezone_name | valid_from | valid_to |
|---|---|---|---|---|---|---|
| i64 | str | str | str | str | datetime[ns] | null |
| 1 | ASML Holding N.V. | ASML HOLDING | Amsterdam | Europe/Amsterdam | 2026-03-04 22:11:36.189862 | null |
| 2 | LVMH Moët Hennessy - Louis Vui… | LVMH | Paris | Europe/Paris | 2026-03-04 22:11:36.189862 | null |
| 3 | Hermès International Société e… | HERMES INTL | Paris | Europe/Paris | 2026-03-04 22:11:36.193940 | null |
| 4 | L'Oréal S.A. | L'OREAL | Paris | Europe/Paris | 2026-03-04 22:11:36.193940 | null |
| 5 | SAP SE | SAP SE | XETRA | Europe/Berlin | 2026-03-04 22:11:36.193940 | null |
Comparison Table | Pandas vs Polars
comparison = """
| Task | Pandas | Polars |
|---|---|---|
| Single column (Series) | `df["col"]` | `df.get_column("col")` |
| Single column (DataFrame) | `df[["col"]]` | `df.select("col")` |
| Multiple columns | `df[["a","b"]]` | `df.select("a","b")` |
| Label-based slice | `df.loc[:, "a":"c"]` | (use explicit list) |
| Position-based | `df.iloc[:, 0:3]` | `df[:, 0:3]` |
| All columns | `df` | `df.select(pl.all())` |
| Exclude columns | `df.drop(columns=[...])` | `df.select(pl.exclude(...))` |
| Numeric columns | `df.select_dtypes("number")` | `df.select(cs.numeric())` |
| String columns | `df.select_dtypes("object")` | `df.select(cs.string())` |
| Temporal columns | `df.select_dtypes("datetime")` | `df.select(cs.temporal())` |
| By specific dtype | `df.select_dtypes(include=...)` | `df.select(cs.by_dtype(...))` |
| By name pattern | `df.filter(regex=...)` | `df.select(pl.col("^regex$"))` |
| Selector by name | (manual list) | `cs.by_name("a","b")` |
| Selector set ops | (not available) | `cs.numeric() - cs.by_dtype(pl.Int64)` |
| Rename | `df.rename(columns={...})` | `df.rename({...})` |
| Alias in expr | (not applicable) | `pl.col("x").alias("y")` |
| Prefix / suffix | `df.add_prefix("p_")` | `cs.numeric().name.prefix("p_")` |
| Reorder | `df[new_order]` | `df.select(new_order)` |
| Drop columns | `df.drop(columns=[...])` | `df.drop("a","b")` |
| Regex select | `df.filter(regex="pattern")` | `pl.col("^pattern$")` |
"""
display(Markdown(comparison))| Task | Pandas | Polars |
|---|---|---|
| Single column (Series) | df["col"] | df.get_column("col") |
| Single column (DataFrame) | df[["col"]] | df.select("col") |
| Multiple columns | df[["a","b"]] | df.select("a","b") |
| Label-based slice | df.loc[:, "a":"c"] | (use explicit list) |
| Position-based | df.iloc[:, 0:3] | df[:, 0:3] |
| All columns | df | df.select(pl.all()) |
| Exclude columns | df.drop(columns=[...]) | df.select(pl.exclude(...)) |
| Numeric columns | df.select_dtypes("number") | df.select(cs.numeric()) |
| String columns | df.select_dtypes("object") | df.select(cs.string()) |
| Temporal columns | df.select_dtypes("datetime") | df.select(cs.temporal()) |
| By specific dtype | df.select_dtypes(include=...) | df.select(cs.by_dtype(...)) |
| By name pattern | df.filter(regex=...) | df.select(pl.col("^regex$")) |
| Selector by name | (manual list) | cs.by_name("a","b") |
| Selector set ops | (not available) | cs.numeric() - cs.by_dtype(pl.Int64) |
| Rename | df.rename(columns={...}) | df.rename({...}) |
| Alias in expr | (not applicable) | pl.col("x").alias("y") |
| Prefix / suffix | df.add_prefix("p_") | cs.numeric().name.prefix("p_") |
| Reorder | df[new_order] | df.select(new_order) |
| Drop columns | df.drop(columns=[...]) | df.drop("a","b") |
| Regex select | df.filter(regex="pattern") | pl.col("^pattern$") |
End of notebook.
Filtering Rows
Row filtering selects subsets of rows based on conditions. Pandas uses boolean indexing (df[mask]), .loc[], and .query(). Polars uses .filter() with expressions. Both support compound conditions, membership tests, range checks, null filtering, and string/datetime accessors.
Performance | Vectorized expressions vs boolean masks
Polars
.filter(pl.col("x") > 100)compiles into a vectorized query plan — the engine processes entire columns at once. Pandasdf[df["x"] > 100]creates an intermediate boolean mask array in memory. For large datasets, Polars filtering is significantly faster and more memory-efficient.
Setup & Data Loading
print("ohlcv :\n", ohlcv_pd.shape, "\n", list(ohlcv_pd.columns))
print("\ndim :\n", dim_pd.shape, "\n", list(dim_pd.columns))
print("\nscores :\n", scores_pd.shape, "\n", list(scores_pd.columns))ohlcv : (66355, 12) [‘id’, ‘symbol’, ‘date’, ‘open’, ‘high’, ‘low’, ‘close’, ‘adj_close’, ‘volume’, ‘dividends’, ‘stock_splits’, ‘is_filled’]
dim : (169, 26) [‘id’, ‘_index’, ‘symbol’, ‘long_name’, ‘short_name’, ‘sector’, ‘sector_key’, ‘industry’, ‘industry_key’, ‘country’, ‘city’, ‘website’, ‘long_business_summary’, ‘exchange’, ‘full_exchange_name’, ‘exchange_timezone_name’, ‘exchange_timezone_short’, ‘currency’, ‘financial_currency’, ‘quote_type’, ‘market’, ‘range_start’, ‘price_data_start’, ‘valid_from’, ‘valid_to’, ‘is_current’]
scores : (466, 36) [‘id’, ‘_index’, ‘symbol’, ‘score_date’, ‘sector’, ‘pe_zscore’, ‘pb_zscore’, ‘ev_ebitda_zscore’, ‘yield_zscore’, ‘relative_value_score’, ‘relative_value_rank’, ‘relative_strength’, ‘sma_50_ratio’, ‘sma_200_ratio’, ‘dist_from_52w_high’, ‘momentum_score’, ‘momentum_rank’, ‘implied_upside’, ‘recommendation_mean’, ‘price_falling_analysts_bullish’, ‘sentiment_score’, ‘sentiment_rank’, ‘composite_score’, ‘composite_rank’, ‘_scored_at’, ‘sma_30_close’, ‘sma_90_close’, ‘market_cap’, ‘index_weight’, ‘short_name’, ‘country’, ‘current_price’, ‘day_change_pct’, ‘five_day_change_pct’, ‘ytd_change_pct’, ‘currency’]
ohlcv_pd.head(3)| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
ohlcv_pl.head(3)| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
Boolean Indexing (Single Condition)
Chained indexing in Pandas
Chained indexing in Pandas —
df[condition]["col"] = valuesilently failsdf[df["close"] > 50]["close"] = 0looks like it works but modifies a copy, not the original DataFrame. Pandas raisesSettingWithCopyWarningbut the change is lost. Always use.loc[]for assignment:df.loc[df["close"] > 50, "close"] = 0.Polars has no chained indexing — all operations return new DataFrames, eliminating this entire class of bugs.
for all conditional assignment in Pandas
Replace any chained write (
df[mask]["col"] = val) with a single.loc[]call:df.loc[df["close"] > 50, "close"] = 0. In Pandas 3+, Copy-on-Write is the default and chained assignment raises a hard error — migrating to.loc[]now is future-proof. In Polars, usepl.when(condition).then(value).otherwise(pl.col("col"))insidewith_columns.
Pandas | Boolean Indexing — bracket notation
Filters ohlcv_pd to rows where the close column exceeds 50 using bracket-notation boolean indexing — the simplest Pandas filter form, returning the first 5 matching OHLCV rows.
# Rows where Close > 50
ohlcv_pd[ohlcv_pd["close"] > 50].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| 3 | 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 | False |
| 4 | 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | False |
Pandas | .loc with a boolean mask
Applies the same close > 50 filter as bracket notation but using .loc[], producing identical results — .loc[] is preferred over bracket notation for any conditional assignment operation to avoid SettingWithCopyWarning.
ohlcv_pd.loc[ohlcv_pd["close"] > 50].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| 3 | 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 | False |
| 4 | 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | False |
Polars | filter
- pl.col: Reference a column by name. The foundation of all Polars expressions.
Filters ohlcv_pl to rows where close > 50 using a pl.col expression inside .filter() — the Polars equivalent of Pandas bracket-notation boolean indexing, returning the first 5 matching rows without the SettingWithCopyWarning risk.
ohlcv_pl.filter(pl.col("close") > 50).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 | false |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | false |
Multiple Conditions (&, |, ~)
Pandas | AND / OR / NOT
Builds three boolean masks on ohlcv_pd: AND (close > 50 and volume > 1M), OR (close < 10 or close > 100), and NOT (volume not above 5M) — each stored in a variable and applied with .loc[] to demonstrate all three Pandas boolean operators.
# AND: close > 50 AND volume > 1_000_000
mask = (ohlcv_pd["close"] > 50) & (ohlcv_pd["volume"] > 1_000_000)
ohlcv_pd.loc[mask].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| 3 | 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 | False |
| 4 | 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | False |
# OR: close < 10 OR close > 100
mask = (ohlcv_pd["close"] < 10) | (ohlcv_pd["close"] > 100)
ohlcv_pd.loc[mask].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2662 | 62088 | ADS.DE | 2021-01-04 | 300.0 | 300.5 | 293.0 | 295.4 | 282.2904 | 440364 | 0.0 | 0.0 | False |
| 2663 | 62089 | ADS.DE | 2021-01-05 | 292.9 | 295.4 | 288.2 | 289.6 | 276.7479 | 436591 | 0.0 | 0.0 | False |
| 2664 | 62090 | ADS.DE | 2021-01-06 | 290.7 | 292.7 | 286.8 | 291.7 | 278.7546 | 392602 | 0.0 | 0.0 | False |
| 2665 | 62091 | ADS.DE | 2021-01-07 | 294.0 | 294.1 | 288.5 | 288.5 | 275.6967 | 362809 | 0.0 | 0.0 | False |
| 2666 | 62092 | ADS.DE | 2021-01-08 | 292.3 | 296.8 | 292.0 | 295.1 | 282.0038 | 425762 | 0.0 | 0.0 | False |
# NOT: rows where volume is NOT above 5_000_000
mask = ~(ohlcv_pd["volume"] > 5_000_000)
ohlcv_pd.loc[mask].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| 3 | 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 | False |
| 4 | 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | False |
Polars | AND / OR / NOT
- pl.col: Reference a column by name. The foundation of all Polars expressions.
Demonstrates AND, OR, and NOT filtering in Polars using pl.col expressions inside .filter() — mirrors the Pandas examples with the same threshold values, applying &, |, and ~ operators directly in the expression context without intermediate mask variables.
ohlcv_pl.filter(
(pl.col("close") > 50) & (pl.col("volume") > 1_000_000)
).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 | false |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | false |
ohlcv_pl.filter(
(pl.col("close") < 10) | (pl.col("close") > 100)
).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 62088 | ADS.DE | 2021-01-04 | 300.0 | 300.5 | 293.0 | 295.4 | 282.2904 | 440364 | 0.0 | 0.0 | false |
| 62089 | ADS.DE | 2021-01-05 | 292.9 | 295.4 | 288.2 | 289.6 | 276.7479 | 436591 | 0.0 | 0.0 | false |
| 62090 | ADS.DE | 2021-01-06 | 290.7 | 292.7 | 286.8 | 291.7 | 278.7546 | 392602 | 0.0 | 0.0 | false |
| 62091 | ADS.DE | 2021-01-07 | 294.0 | 294.1 | 288.5 | 288.5 | 275.6967 | 362809 | 0.0 | 0.0 | false |
| 62092 | ADS.DE | 2021-01-08 | 292.3 | 296.8 | 292.0 | 295.1 | 282.0038 | 425762 | 0.0 | 0.0 | false |
ohlcv_pl.filter(
~(pl.col("volume") > 5_000_000)
).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 | false |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | false |
query() (Pandas Only)
ohlcv_pd.query("close > 50 and volume > 1_000_000").head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| 3 | 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 | False |
| 4 | 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | False |
# Using variables with @
threshold = 80
ohlcv_pd.query("close > @threshold").head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2662 | 62088 | ADS.DE | 2021-01-04 | 300.0 | 300.5 | 293.0 | 295.4 | 282.2904 | 440364 | 0.0 | 0.0 | False |
| 2663 | 62089 | ADS.DE | 2021-01-05 | 292.9 | 295.4 | 288.2 | 289.6 | 276.7479 | 436591 | 0.0 | 0.0 | False |
| 2664 | 62090 | ADS.DE | 2021-01-06 | 290.7 | 292.7 | 286.8 | 291.7 | 278.7546 | 392602 | 0.0 | 0.0 | False |
| 2665 | 62091 | ADS.DE | 2021-01-07 | 294.0 | 294.1 | 288.5 | 288.5 | 275.6967 | 362809 | 0.0 | 0.0 | False |
| 2666 | 62092 | ADS.DE | 2021-01-08 | 292.3 | 296.8 | 292.0 | 295.1 | 282.0038 | 425762 | 0.0 | 0.0 | False |
# String column comparisons in query
tickers = ["SIE.DE", "SAP.DE"]
ohlcv_pd.query("symbol in @tickers").head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 55738 | 5301 | SAP.DE | 2021-01-04 | 108.10 | 108.50 | 104.78 | 105.32 | 97.0102 | 2928515 | 0.0 | 0.0 | False |
| 55739 | 5302 | SAP.DE | 2021-01-05 | 104.98 | 106.20 | 104.46 | 105.04 | 96.7523 | 2798888 | 0.0 | 0.0 | False |
| 55740 | 5303 | SAP.DE | 2021-01-06 | 105.14 | 106.26 | 103.60 | 105.48 | 97.1576 | 3018802 | 0.0 | 0.0 | False |
| 55741 | 5304 | SAP.DE | 2021-01-07 | 105.58 | 105.70 | 104.04 | 104.52 | 96.2734 | 3176143 | 0.0 | 0.0 | False |
| 55742 | 5305 | SAP.DE | 2021-01-08 | 105.14 | 106.72 | 105.04 | 106.18 | 97.8024 | 3068744 | 0.0 | 0.0 | False |
isin / is_in
Pandas | filter by ticker list with isin
Defines a list of three German ticker symbols and filters ohlcv_pd to only rows matching those tickers using .isin() — returning the first 5 OHLCV rows for SIE.DE, SAP.DE, or BAS.DE.
target_tickers = ["SIE.DE", "SAP.DE", "BAS.DE"]
ohlcv_pd[ohlcv_pd["symbol"].isin(target_tickers)].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11965 | 52834 | BAS.DE | 2021-01-04 | 65.48 | 66.07 | 64.43 | 64.89 | 47.4862 | 2741508 | 0.0 | 0.0 | False |
| 11966 | 52835 | BAS.DE | 2021-01-05 | 64.30 | 65.47 | 63.26 | 64.40 | 47.1277 | 2770337 | 0.0 | 0.0 | False |
| 11967 | 52836 | BAS.DE | 2021-01-06 | 65.24 | 67.55 | 65.14 | 67.37 | 49.3011 | 5187251 | 0.0 | 0.0 | False |
| 11968 | 52837 | BAS.DE | 2021-01-07 | 67.93 | 68.53 | 67.13 | 68.41 | 50.0622 | 3655366 | 0.0 | 0.0 | False |
| 11969 | 52838 | BAS.DE | 2021-01-08 | 69.00 | 69.24 | 68.01 | 68.58 | 50.1866 | 3035733 | 0.0 | 0.0 | False |
Polars | is_in
Applies the same three-ticker filter as the Pandas example using pl.col("symbol").is_in(target_tickers) inside .filter() — reusing the same target list to confirm parity between Pandas .isin() and Polars .is_in().
target_tickers = ["SIE.DE", "SAP.DE", "BAS.DE"]
ohlcv_pl.filter(pl.col("symbol").is_in(target_tickers)).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 52834 | BAS.DE | 2021-01-04 | 65.48 | 66.07 | 64.43 | 64.89 | 47.4862 | 2741508 | 0.0 | 0.0 | false |
| 52835 | BAS.DE | 2021-01-05 | 64.3 | 65.47 | 63.26 | 64.4 | 47.1277 | 2770337 | 0.0 | 0.0 | false |
| 52836 | BAS.DE | 2021-01-06 | 65.24 | 67.55 | 65.14 | 67.37 | 49.3011 | 5187251 | 0.0 | 0.0 | false |
| 52837 | BAS.DE | 2021-01-07 | 67.93 | 68.53 | 67.13 | 68.41 | 50.0622 | 3655366 | 0.0 | 0.0 | false |
| 52838 | BAS.DE | 2021-01-08 | 69.0 | 69.24 | 68.01 | 68.58 | 50.1866 | 3035733 | 0.0 | 0.0 | false |
between / is_between
Pandas | close price in range 40–60 with between
Filters ohlcv_pd to rows where the close price falls in the closed interval [40, 60] using .between() — targets the lower-priced tier of the Euro Stoxx 50 universe, returning the first 5 matching rows.
ohlcv_pd[ohlcv_pd["close"].between(40, 60)].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| 3 | 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 | False |
| 4 | 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | False |
Polars | is_between
Demonstrates is_between() for both a numeric range (close between 40 and 60) and a date range (date between 2023-01-01 and 2023-06-30), using pl.lit(...).str.to_date() to convert string literals to dates for the second filter.
ohlcv_pl.filter(pl.col("close").is_between(40, 60)).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 | false |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | false |
# Date range filtering — Polars
ohlcv_pl.filter(
pl.col("date").is_between(
pl.lit("2023-01-01").str.to_date(),
pl.lit("2023-06-30").str.to_date(),
)
).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21675 | ABI.BR | 2023-01-02 | 56.63 | 57.09 | 56.43 | 56.9 | 54.2453 | 608437 | 0.0 | 0.0 | false |
| 21676 | ABI.BR | 2023-01-03 | 56.79 | 57.76 | 56.72 | 56.84 | 54.1881 | 1164809 | 0.0 | 0.0 | false |
| 21677 | ABI.BR | 2023-01-04 | 56.96 | 58.02 | 56.94 | 58.02 | 55.313 | 1835512 | 0.0 | 0.0 | false |
| 21678 | ABI.BR | 2023-01-05 | 57.69 | 57.98 | 57.0 | 57.14 | 54.4741 | 1324250 | 0.0 | 0.0 | false |
| 21679 | ABI.BR | 2023-01-06 | 57.23 | 57.47 | 57.03 | 57.44 | 54.7601 | 1100010 | 0.0 | 0.0 | false |
Null / NaN Filtering
Pandas | Null / NaN Filtering
Filters scores_pd to rows where ev_ebitda_zscore is null using .isna(), then to rows where it is not null using .notna() — identifies which index constituents lack EV/EBITDA data (financial institutions without an enterprise value ratio).
# Rows where a column IS null
scores_pd[scores_pd["ev_ebitda_zscore"].isna()].head()| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.261140 | NaN | 2.388962 | 1.521163 | 1 | 0.016123 | 1.009090 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | False | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085000 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.320 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 8 | 188 | euro_stoxx_50 | SAN.MC | 2026-03-04 | Financial Services | 0.458599 | 0.499398 | NaN | -1.107590 | -0.049864 | 33 | 0.393197 | 0.953824 | 1.139519 | 0.113499 | 0.519307 | 14 | 0.224704 | 1.70000 | False | 0.448776 | 15 | 0.306073 | 9 | 2026-03-04 22:40:25.489180 | 10.604900 | 9.919500 | 145955749888 | 0.028570 | BANCO SANTANDER S.A. | Spain | 9.982 | 0.038818 | -0.105876 | -0.008739 | EUR |
| 15 | 176 | euro_stoxx_50 | ISP.MI | 2026-03-04 | Financial Services | 0.366619 | 0.488302 | NaN | 0.723361 | 0.526094 | 13 | -0.065247 | 0.922492 | 0.990855 | 0.119662 | -0.122626 | 33 | 0.254150 | 2.00000 | False | 0.146959 | 21 | 0.183476 | 16 | 2026-03-04 22:40:25.489180 | 5.834933 | 5.769367 | 94268317696 | 0.018452 | INTESA SANPAOLO | Italy | 5.422 | 0.018216 | -0.067103 | -0.084276 | EUR |
| 16 | 195 | euro_stoxx_50 | UCG.MI | 2026-03-04 | Financial Services | 0.452501 | 0.355225 | NaN | -0.260674 | 0.182351 | 26 | 0.085867 | 0.950889 | 1.054430 | 0.137862 | 0.123670 | 22 | 0.261521 | 1.94444 | False | 0.240819 | 18 | 0.182280 | 17 | 2026-03-04 22:40:25.489180 | 73.253333 | 68.983556 | 103066501120 | 0.020174 | UNICREDIT | Italy | 68.790 | 0.027483 | -0.072161 | -0.030034 | EUR |
| 20 | 175 | euro_stoxx_50 | INGA.AS | 2026-03-04 | Financial Services | 0.379917 | 0.727879 | NaN | -0.240509 | 0.289096 | 21 | 0.113086 | 0.946227 | 1.070815 | 0.118737 | 0.192271 | 21 | 0.201459 | 2.10526 | False | -0.145505 | 30 | 0.111954 | 21 | 2026-03-04 22:40:25.489180 | 24.766000 | 23.632333 | 67454382080 | 0.013204 | ING GROEP N.V. | Netherlands | 23.305 | 0.018798 | -0.066867 | -0.029363 | EUR |
# Rows where a column is NOT null
scores_pd[scores_pd["ev_ebitda_zscore"].notna()].head()| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 168 | euro_stoxx_50 | DTE.DE | 2026-03-04 | Communication Services | 0.326587 | 0.387463 | 0.379532 | -0.127867 | 0.241429 | 24 | -0.205598 | 1.120650 | 1.112416 | 0.055524 | 0.685752 | 8 | 0.121212 | 1.33333 | False | 0.617835 | 10 | 0.515005 | 2 | 2026-03-04 22:40:25.489180 | 30.838000 | 28.554556 | 164294311936 | 0.032159 | DEUTSCHE TELEKOM AG | Germany | 33.000 | 0.011649 | -0.019608 | 0.193059 | EUR |
| 2 | 174 | euro_stoxx_50 | IFX.DE | 2026-03-04 | Technology | 0.509398 | 0.637215 | 0.677068 | -0.696662 | 0.281755 | 22 | 0.000965 | 1.048244 | 1.198626 | 0.088845 | 0.675764 | 9 | 0.126408 | 1.37500 | False | 0.579187 | 11 | 0.512235 | 3 | 2026-03-04 22:40:25.489180 | 43.480333 | 38.855556 | 57222533120 | 0.011201 | INFINEON TECHNOLOGIES AG | Germany | 43.945 | 0.054343 | -0.066490 | 0.164723 | EUR |
| 3 | 172 | euro_stoxx_50 | ENR.DE | 2026-03-04 | Industrials | -0.902738 | -0.743338 | -1.693212 | -1.326075 | -1.166341 | 46 | 1.645455 | 1.137007 | 1.474095 | 0.051850 | 2.541889 | 1 | 0.075269 | 1.80000 | False | -0.123264 | 29 | 0.417428 | 4 | 2026-03-04 22:40:25.489180 | 155.675000 | 129.122000 | 139207262208 | 0.027249 | Siemens Energy AG | Germany | 162.750 | 0.047297 | -0.039256 | 0.351744 | EUR |
| 4 | 149 | euro_stoxx_50 | ABI.BR | 2026-03-04 | Consumer Defensive | 0.474084 | 0.844075 | 0.552739 | -0.975005 | 0.223973 | 25 | -0.029791 | 1.058542 | 1.142783 | 0.063063 | 0.651891 | 10 | 0.186198 | 1.69231 | False | 0.344755 | 17 | 0.406873 | 5 | 2026-03-04 22:40:25.489180 | 64.342000 | 57.869333 | 125566156800 | 0.024579 | AB INBEV | Belgium | 64.480 | -0.017073 | -0.040762 | 0.174499 | EUR |
| 5 | 196 | euro_stoxx_50 | VOW.DE | 2026-03-04 | Consumer Cyclical | 1.166221 | 0.940273 | 0.379830 | 1.528891 | 1.003804 | 2 | -0.292748 | 0.929392 | 0.969628 | 0.180805 | -0.411969 | 37 | 0.297071 | NaN | False | 0.555357 | 12 | 0.382397 | 6 | 2026-03-04 22:40:25.489180 | 102.286667 | 101.225556 | 47923826688 | 0.009381 | VOLKSWAGEN AG | Germany | 95.600 | 0.013786 | -0.048756 | -0.090390 | EUR |
Polars | Null / NaN Filtering
- pl.col: Reference a column by name. The foundation of all Polars expressions.
Filters scores_pl to null rows with .is_null() and non-null rows with .is_not_null() on ev_ebitda_zscore — the Polars equivalents of Pandas .isna() / .notna(), confirming the same financial institutions are identified as lacking EV/EBITDA data.
scores_pl.filter(pl.col("ev_ebitda_zscore").is_null()).head()| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | str | date | str | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool | f64 | i64 | f64 | i64 | datetime[ns] | f64 | f64 | i64 | f64 | str | str | f64 | f64 | f64 | f64 | str |
| 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.26114 | null | 2.388962 | 1.521163 | 1 | 0.016123 | 1.00909 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | false | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.32 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 188 | euro_stoxx_50 | SAN.MC | 2026-03-04 | Financial Services | 0.458599 | 0.499398 | null | -1.10759 | -0.049864 | 33 | 0.393197 | 0.953824 | 1.139519 | 0.113499 | 0.519307 | 14 | 0.224704 | 1.7 | false | 0.448776 | 15 | 0.306073 | 9 | 2026-03-04 22:40:25.489180 | 10.6049 | 9.9195 | 145955749888 | 0.02857 | BANCO SANTANDER S.A. | Spain | 9.982 | 0.038818 | -0.105876 | -0.008739 | EUR |
| 176 | euro_stoxx_50 | ISP.MI | 2026-03-04 | Financial Services | 0.366619 | 0.488302 | null | 0.723361 | 0.526094 | 13 | -0.065247 | 0.922492 | 0.990855 | 0.119662 | -0.122626 | 33 | 0.25415 | 2.0 | false | 0.146959 | 21 | 0.183476 | 16 | 2026-03-04 22:40:25.489180 | 5.834933 | 5.769367 | 94268317696 | 0.018452 | INTESA SANPAOLO | Italy | 5.422 | 0.018216 | -0.067103 | -0.084276 | EUR |
| 195 | euro_stoxx_50 | UCG.MI | 2026-03-04 | Financial Services | 0.452501 | 0.355225 | null | -0.260674 | 0.182351 | 26 | 0.085867 | 0.950889 | 1.05443 | 0.137862 | 0.12367 | 22 | 0.261521 | 1.94444 | false | 0.240819 | 18 | 0.18228 | 17 | 2026-03-04 22:40:25.489180 | 73.253333 | 68.983556 | 103066501120 | 0.020174 | UNICREDIT | Italy | 68.79 | 0.027483 | -0.072161 | -0.030034 | EUR |
| 175 | euro_stoxx_50 | INGA.AS | 2026-03-04 | Financial Services | 0.379917 | 0.727879 | null | -0.240509 | 0.289096 | 21 | 0.113086 | 0.946227 | 1.070815 | 0.118737 | 0.192271 | 21 | 0.201459 | 2.10526 | false | -0.145505 | 30 | 0.111954 | 21 | 2026-03-04 22:40:25.489180 | 24.766 | 23.632333 | 67454382080 | 0.013204 | ING GROEP N.V. | Netherlands | 23.305 | 0.018798 | -0.066867 | -0.029363 | EUR |
scores_pl.filter(pl.col("ev_ebitda_zscore").is_not_null()).head()| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | str | date | str | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool | f64 | i64 | f64 | i64 | datetime[ns] | f64 | f64 | i64 | f64 | str | str | f64 | f64 | f64 | f64 | str |
| 168 | euro_stoxx_50 | DTE.DE | 2026-03-04 | Communication Services | 0.326587 | 0.387463 | 0.379532 | -0.127867 | 0.241429 | 24 | -0.205598 | 1.12065 | 1.112416 | 0.055524 | 0.685752 | 8 | 0.121212 | 1.33333 | false | 0.617835 | 10 | 0.515005 | 2 | 2026-03-04 22:40:25.489180 | 30.838 | 28.554556 | 164294311936 | 0.032159 | DEUTSCHE TELEKOM AG | Germany | 33.0 | 0.011649 | -0.019608 | 0.193059 | EUR |
| 174 | euro_stoxx_50 | IFX.DE | 2026-03-04 | Technology | 0.509398 | 0.637215 | 0.677068 | -0.696662 | 0.281755 | 22 | 0.000965 | 1.048244 | 1.198626 | 0.088845 | 0.675764 | 9 | 0.126408 | 1.375 | false | 0.579187 | 11 | 0.512235 | 3 | 2026-03-04 22:40:25.489180 | 43.480333 | 38.855556 | 57222533120 | 0.011201 | INFINEON TECHNOLOGIES AG | Germany | 43.945 | 0.054343 | -0.06649 | 0.164723 | EUR |
| 172 | euro_stoxx_50 | ENR.DE | 2026-03-04 | Industrials | -0.902738 | -0.743338 | -1.693212 | -1.326075 | -1.166341 | 46 | 1.645455 | 1.137007 | 1.474095 | 0.05185 | 2.541889 | 1 | 0.075269 | 1.8 | false | -0.123264 | 29 | 0.417428 | 4 | 2026-03-04 22:40:25.489180 | 155.675 | 129.122 | 139207262208 | 0.027249 | Siemens Energy AG | Germany | 162.75 | 0.047297 | -0.039256 | 0.351744 | EUR |
| 149 | euro_stoxx_50 | ABI.BR | 2026-03-04 | Consumer Defensive | 0.474084 | 0.844075 | 0.552739 | -0.975005 | 0.223973 | 25 | -0.029791 | 1.058542 | 1.142783 | 0.063063 | 0.651891 | 10 | 0.186198 | 1.69231 | false | 0.344755 | 17 | 0.406873 | 5 | 2026-03-04 22:40:25.489180 | 64.342 | 57.869333 | 125566156800 | 0.024579 | AB INBEV | Belgium | 64.48 | -0.017073 | -0.040762 | 0.174499 | EUR |
| 196 | euro_stoxx_50 | VOW.DE | 2026-03-04 | Consumer Cyclical | 1.166221 | 0.940273 | 0.37983 | 1.528891 | 1.003804 | 2 | -0.292748 | 0.929392 | 0.969628 | 0.180805 | -0.411969 | 37 | 0.297071 | null | false | 0.555357 | 12 | 0.382397 | 6 | 2026-03-04 22:40:25.489180 | 102.286667 | 101.225556 | 47923826688 | 0.009381 | VOLKSWAGEN AG | Germany | 95.6 | 0.013786 | -0.048756 | -0.09039 | EUR |
where / mask (Pandas) vs when / then / otherwise (Polars)
These do not strictly filter rows — they replace values conditionally while keeping all rows.
Pandas | where — keep values where True, replace with NaN where False
Applies .where() to the close column, keeping the original price where volume > 1M and replacing it with NaN otherwise — demonstrating conditional value preservation that retains all rows while masking low-volume prices.
# Keep close where volume > 1M, else NaN
ohlcv_pd["close"].where(ohlcv_pd["volume"] > 1_000_000).head(10)| close | |
|---|---|
| 0 | 57.21 |
| 1 | 57.18 |
| 2 | 58.77 |
| 3 | 58.40 |
| 4 | 57.86 |
| 5 | 56.61 |
| 6 | 56.51 |
| 7 | 56.48 |
| 8 | 56.96 |
| 9 | 56.74 |
Pandas | mask — opposite of where (replace where True)
Applies .mask() to the close column, replacing prices with NaN where volume > 5M — the inverse of .where(), masking out high-volume spikes rather than preserving low-volume entries.
# Replace close with NaN where volume > 5M
ohlcv_pd["close"].mask(ohlcv_pd["volume"] > 5_000_000).head(10)| close | |
|---|---|
| 0 | 57.21 |
| 1 | 57.18 |
| 2 | 58.77 |
| 3 | 58.40 |
| 4 | 57.86 |
| 5 | 56.61 |
| 6 | 56.51 |
| 7 | 56.48 |
| 8 | 56.96 |
| 9 | 56.74 |
Polars | when / then / otherwise
- With Columns: Add new columns or replace existing ones. All original columns are kept.
- pl.col: Reference a column by name. The foundation of all Polars expressions.
- pl.lit: Create a constant/literal value as an expression.
Creates a close_filtered column keeping close where volume > 1M and None otherwise, then chains multiple when/then to bucket prices into “high”, “mid”, or “low” — both computed as new columns via with_columns() without dropping any rows.
ohlcv_pl.with_columns(
pl.when(pl.col("volume") > 1_000_000)
.then(pl.col("close"))
.otherwise(pl.lit(None))
.alias("close_filtered")
).select("symbol", "date", "close", "volume", "close_filtered").head(10)| symbol | date | close | volume | close_filtered |
|---|---|---|---|---|
| str | date | f64 | i64 | f64 |
| ABI.BR | 2021-01-04 | 57.21 | 1513937 | 57.21 |
| ABI.BR | 2021-01-05 | 57.18 | 1382722 | 57.18 |
| ABI.BR | 2021-01-06 | 58.77 | 1370204 | 58.77 |
| ABI.BR | 2021-01-07 | 58.4 | 1469911 | 58.4 |
| ABI.BR | 2021-01-08 | 57.86 | 1428681 | 57.86 |
| ABI.BR | 2021-01-11 | 56.61 | 1518079 | 56.61 |
| ABI.BR | 2021-01-12 | 56.51 | 1649991 | 56.51 |
| ABI.BR | 2021-01-13 | 56.48 | 1090806 | 56.48 |
| ABI.BR | 2021-01-14 | 56.96 | 1523045 | 56.96 |
| ABI.BR | 2021-01-15 | 56.74 | 1769988 | 56.74 |
# Multiple conditions with when/then chaining (Polars)
ohlcv_pl.with_columns(
pl.when(pl.col("close") > 100)
.then(pl.lit("high"))
.when(pl.col("close") > 50)
.then(pl.lit("mid"))
.otherwise(pl.lit("low"))
.alias("price_bucket")
).select("symbol", "date", "close", "price_bucket").head(10)| symbol | date | close | price_bucket |
|---|---|---|---|
| str | date | f64 | str |
| ABI.BR | 2021-01-04 | 57.21 | mid |
| ABI.BR | 2021-01-05 | 57.18 | mid |
| ABI.BR | 2021-01-06 | 58.77 | mid |
| ABI.BR | 2021-01-07 | 58.4 | mid |
| ABI.BR | 2021-01-08 | 57.86 | mid |
| ABI.BR | 2021-01-11 | 56.61 | mid |
| ABI.BR | 2021-01-12 | 56.51 | mid |
| ABI.BR | 2021-01-13 | 56.48 | mid |
| ABI.BR | 2021-01-14 | 56.96 | mid |
| ABI.BR | 2021-01-15 | 56.74 | mid |
String Accessor Filtering
Pandas | String Accessor Filtering — .str
- String Ops: Text manipulation via .str accessor: contains, split, replace, extract.
Filters ohlcv_pd via the .str accessor: first to tickers starting with “S” using .str.startswith() (e.g., SAF.PA, SAP.DE, SAN.MC), then to tickers containing “DE” using .str.contains() — selecting German-exchange stocks by exchange suffix.
# Tickers that start with "S"
ohlcv_pd[ohlcv_pd["symbol"].str.startswith("S")].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 51747 | 18512 | SAF.PA | 2021-01-04 | 117.35 | 121.00 | 116.1 | 116.15 | 111.6447 | 658764 | 0.0 | 0.0 | False |
| 51748 | 18513 | SAF.PA | 2021-01-05 | 114.90 | 116.95 | 114.8 | 116.40 | 111.8850 | 588765 | 0.0 | 0.0 | False |
| 51749 | 18514 | SAF.PA | 2021-01-06 | 117.55 | 117.55 | 115.4 | 116.30 | 111.7888 | 581543 | 0.0 | 0.0 | False |
| 51750 | 18515 | SAF.PA | 2021-01-07 | 117.05 | 117.30 | 114.8 | 115.80 | 111.3082 | 635605 | 0.0 | 0.0 | False |
| 51751 | 18516 | SAF.PA | 2021-01-08 | 116.90 | 117.00 | 115.0 | 116.35 | 111.8369 | 688460 | 0.0 | 0.0 | False |
# Tickers containing "DE"
ohlcv_pd[ohlcv_pd["symbol"].str.contains("DE")].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2662 | 62088 | ADS.DE | 2021-01-04 | 300.0 | 300.5 | 293.0 | 295.4 | 282.2904 | 440364 | 0.0 | 0.0 | False |
| 2663 | 62089 | ADS.DE | 2021-01-05 | 292.9 | 295.4 | 288.2 | 289.6 | 276.7479 | 436591 | 0.0 | 0.0 | False |
| 2664 | 62090 | ADS.DE | 2021-01-06 | 290.7 | 292.7 | 286.8 | 291.7 | 278.7546 | 392602 | 0.0 | 0.0 | False |
| 2665 | 62091 | ADS.DE | 2021-01-07 | 294.0 | 294.1 | 288.5 | 288.5 | 275.6967 | 362809 | 0.0 | 0.0 | False |
| 2666 | 62092 | ADS.DE | 2021-01-08 | 292.3 | 296.8 | 292.0 | 295.1 | 282.0038 | 425762 | 0.0 | 0.0 | False |
Polars | String Accessor Filtering — .str
- String Ops: Text manipulation via .str accessor: contains, split, replace, extract.
- pl.col: Reference a column by name. The foundation of all Polars expressions.
Applies the same string filters as the Pandas example using str.starts_with() and str.contains() inside pl.col(...).filter() — note starts_with (no underscore) vs Pandas’ startswith, returning identical ticker subsets.
ohlcv_pl.filter(pl.col("symbol").str.starts_with("S")).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 18512 | SAF.PA | 2021-01-04 | 117.35 | 121.0 | 116.1 | 116.15 | 111.6447 | 658764 | 0.0 | 0.0 | false |
| 18513 | SAF.PA | 2021-01-05 | 114.9 | 116.95 | 114.8 | 116.4 | 111.885 | 588765 | 0.0 | 0.0 | false |
| 18514 | SAF.PA | 2021-01-06 | 117.55 | 117.55 | 115.4 | 116.3 | 111.7888 | 581543 | 0.0 | 0.0 | false |
| 18515 | SAF.PA | 2021-01-07 | 117.05 | 117.3 | 114.8 | 115.8 | 111.3082 | 635605 | 0.0 | 0.0 | false |
| 18516 | SAF.PA | 2021-01-08 | 116.9 | 117.0 | 115.0 | 116.35 | 111.8369 | 688460 | 0.0 | 0.0 | false |
ohlcv_pl.filter(pl.col("symbol").str.contains("DE")).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 62088 | ADS.DE | 2021-01-04 | 300.0 | 300.5 | 293.0 | 295.4 | 282.2904 | 440364 | 0.0 | 0.0 | false |
| 62089 | ADS.DE | 2021-01-05 | 292.9 | 295.4 | 288.2 | 289.6 | 276.7479 | 436591 | 0.0 | 0.0 | false |
| 62090 | ADS.DE | 2021-01-06 | 290.7 | 292.7 | 286.8 | 291.7 | 278.7546 | 392602 | 0.0 | 0.0 | false |
| 62091 | ADS.DE | 2021-01-07 | 294.0 | 294.1 | 288.5 | 288.5 | 275.6967 | 362809 | 0.0 | 0.0 | false |
| 62092 | ADS.DE | 2021-01-08 | 292.3 | 296.8 | 292.0 | 295.1 | 282.0038 | 425762 | 0.0 | 0.0 | false |
Datetime Accessor Filtering
Pandas | Datetime Accessor Filtering — .dt
- DateTime Accessor: Extract date parts: .dt.year(), .dt.month(), .dt.weekday().
- Parse Dates: Convert strings to datetime objects (Pandas).
Converts the date column to datetime with pd.to_datetime() (required in Pandas before .dt access), then filters to January rows using .dt.month == 1 — demonstrating date-part extraction for seasonal or calendar-based filtering.
# Ensure date is datetime
ohlcv_pd["date"] = pd.to_datetime(ohlcv_pd["date"])
# Filter rows in January
ohlcv_pd[ohlcv_pd["date"].dt.month == 1].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| 3 | 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 | False |
| 4 | 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | False |
# Filter by year
ohlcv_pd[ohlcv_pd["date"].dt.year == 2023].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 515 | 21675 | ABI.BR | 2023-01-02 | 56.63 | 57.09 | 56.43 | 56.90 | 54.2453 | 608437 | 0.0 | 0.0 | False |
| 516 | 21676 | ABI.BR | 2023-01-03 | 56.79 | 57.76 | 56.72 | 56.84 | 54.1881 | 1164809 | 0.0 | 0.0 | False |
| 517 | 21677 | ABI.BR | 2023-01-04 | 56.96 | 58.02 | 56.94 | 58.02 | 55.3130 | 1835512 | 0.0 | 0.0 | False |
| 518 | 21678 | ABI.BR | 2023-01-05 | 57.69 | 57.98 | 57.00 | 57.14 | 54.4741 | 1324250 | 0.0 | 0.0 | False |
| 519 | 21679 | ABI.BR | 2023-01-06 | 57.23 | 57.47 | 57.03 | 57.44 | 54.7601 | 1100010 | 0.0 | 0.0 | False |
# Filter by day of week (Monday=0)
ohlcv_pd[ohlcv_pd["date"].dt.dayofweek == 0].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 5 | 21165 | ABI.BR | 2021-01-11 | 57.73 | 57.81 | 56.39 | 56.61 | 53.0142 | 1518079 | 0.0 | 0.0 | False |
| 10 | 21170 | ABI.BR | 2021-01-18 | 56.25 | 57.30 | 56.20 | 57.08 | 53.4544 | 730298 | 0.0 | 0.0 | False |
| 15 | 21175 | ABI.BR | 2021-01-25 | 54.77 | 54.80 | 52.89 | 53.17 | 49.7927 | 1974547 | 0.0 | 0.0 | False |
| 20 | 21180 | ABI.BR | 2021-02-01 | 52.32 | 53.30 | 52.15 | 52.58 | 49.2402 | 1543605 | 0.0 | 0.0 | False |
Polars | Datetime Accessor Filtering — .dt
- DateTime Accessor: Extract date parts: .dt.year(), .dt.month(), .dt.weekday().
- pl.col: Reference a column by name. The foundation of all Polars expressions.
Filters ohlcv_pl by month (January), year (2023), and weekday (Monday = 1 in Polars) using pl.col("date").dt.method() — note Polars requires function-call syntax (.dt.month()) where Pandas uses attribute access (.dt.month).
ohlcv_pl.filter(pl.col("date").dt.month() == 1).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 | false |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | false |
ohlcv_pl.filter(pl.col("date").dt.year() == 2023).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21675 | ABI.BR | 2023-01-02 | 56.63 | 57.09 | 56.43 | 56.9 | 54.2453 | 608437 | 0.0 | 0.0 | false |
| 21676 | ABI.BR | 2023-01-03 | 56.79 | 57.76 | 56.72 | 56.84 | 54.1881 | 1164809 | 0.0 | 0.0 | false |
| 21677 | ABI.BR | 2023-01-04 | 56.96 | 58.02 | 56.94 | 58.02 | 55.313 | 1835512 | 0.0 | 0.0 | false |
| 21678 | ABI.BR | 2023-01-05 | 57.69 | 57.98 | 57.0 | 57.14 | 54.4741 | 1324250 | 0.0 | 0.0 | false |
| 21679 | ABI.BR | 2023-01-06 | 57.23 | 57.47 | 57.03 | 57.44 | 54.7601 | 1100010 | 0.0 | 0.0 | false |
ohlcv_pl.filter(pl.col("date").dt.weekday() == 1).head() # Monday=1 in Polars| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21165 | ABI.BR | 2021-01-11 | 57.73 | 57.81 | 56.39 | 56.61 | 53.0142 | 1518079 | 0.0 | 0.0 | false |
| 21170 | ABI.BR | 2021-01-18 | 56.25 | 57.3 | 56.2 | 57.08 | 53.4544 | 730298 | 0.0 | 0.0 | false |
| 21175 | ABI.BR | 2021-01-25 | 54.77 | 54.8 | 52.89 | 53.17 | 49.7927 | 1974547 | 0.0 | 0.0 | false |
| 21180 | ABI.BR | 2021-02-01 | 52.32 | 53.3 | 52.15 | 52.58 | 49.2402 | 1543605 | 0.0 | 0.0 | false |
head, tail, slice, sample
# Pandas
print("head(3):\n", ohlcv_pd.head(3), "\n")
print("tail(3):\n", ohlcv_pd.tail(3))head(3):
id symbol date open high low close adj_close volume
0 21160 ABI.BR 2021-01-04 58.15 58.85 56.78 57.21 53.5761 1513937
1 21161 ABI.BR 2021-01-05 56.90 57.98 56.75 57.18 53.5480 1382722
2 21162 ABI.BR 2021-01-06 57.96 58.94 57.39 58.77 55.0370 1370204
dividends stock_splits is_filled 0 0.0 0.0 False 1 0.0 0.0 False 2 0.0 0.0 False
tail(3):
id symbol date open high low close adj_close
66352 66876 WKL.AS 2026-03-10 68.8 69.16 66.34 67.16 67.16
66353 66877 WKL.AS 2026-03-11 67.5 69.60 67.02 67.22 67.22
66354 66929 WKL.AS 2026-03-12 67.0 67.54 66.28 67.32 67.32
volume dividends stock_splits is_filled 66352 1355645 0.0 0.0 False 66353 1142531 0.0 0.0 False 66354 210379 0.0 0.0 False
# Pandas — iloc slicing
ohlcv_pd.iloc[10:15]| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 10 | 21170 | ABI.BR | 2021-01-18 | 56.25 | 57.30 | 56.20 | 57.08 | 53.4544 | 730298 | 0.0 | 0.0 | False |
| 11 | 21171 | ABI.BR | 2021-01-19 | 57.10 | 57.26 | 56.26 | 56.35 | 52.7707 | 1116570 | 0.0 | 0.0 | False |
| 12 | 21172 | ABI.BR | 2021-01-20 | 56.35 | 56.77 | 56.00 | 56.24 | 52.6677 | 1226516 | 0.0 | 0.0 | False |
| 13 | 21173 | ABI.BR | 2021-01-21 | 56.20 | 56.55 | 55.31 | 55.31 | 51.7968 | 1404283 | 0.0 | 0.0 | False |
| 14 | 21174 | ABI.BR | 2021-01-22 | 55.28 | 55.28 | 54.12 | 54.78 | 51.3005 | 1557287 | 0.0 | 0.0 | False |
# Pandas — sample
ohlcv_pd.sample(5, random_state=42)| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 43053 | 38920 | MUV2.DE | 2023-03-29 | 320.0000 | 322.600 | 318.400 | 322.4000 | 290.3200 | 195809 | 0.0 | 0.0 | False |
| 56209 | 5772 | SAP.DE | 2022-11-03 | 95.9200 | 96.340 | 95.080 | 95.5100 | 91.9052 | 1424973 | 0.0 | 0.0 | False |
| 53515 | 11015 | SAN.MC | 2022-09-15 | 2.5975 | 2.686 | 2.597 | 2.6765 | 2.3373 | 70158349 | 0.0 | 0.0 | False |
| 6498 | 28954 | AI.PA | 2025-08-12 | 173.3200 | 174.440 | 172.800 | 173.6800 | 173.6800 | 415652 | 0.0 | 0.0 | False |
| 63527 | 24956 | UCG.MI | 2025-07-07 | 56.4600 | 57.350 | 56.440 | 57.3500 | 56.0468 | 4705567 | 0.0 | 0.0 | False |
# Polars
ohlcv_pl.head(3)| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
ohlcv_pl.tail(3)| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 66876 | WKL.AS | 2026-03-10 | 68.8 | 69.16 | 66.34 | 67.16 | 67.16 | 1355645 | 0.0 | 0.0 | false |
| 66877 | WKL.AS | 2026-03-11 | 67.5 | 69.6 | 67.02 | 67.22 | 67.22 | 1142531 | 0.0 | 0.0 | false |
| 66929 | WKL.AS | 2026-03-12 | 67.0 | 67.54 | 66.28 | 67.32 | 67.32 | 210379 | 0.0 | 0.0 | false |
ohlcv_pl.slice(10, 5) # offset, length| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21170 | ABI.BR | 2021-01-18 | 56.25 | 57.3 | 56.2 | 57.08 | 53.4544 | 730298 | 0.0 | 0.0 | false |
| 21171 | ABI.BR | 2021-01-19 | 57.1 | 57.26 | 56.26 | 56.35 | 52.7707 | 1116570 | 0.0 | 0.0 | false |
| 21172 | ABI.BR | 2021-01-20 | 56.35 | 56.77 | 56.0 | 56.24 | 52.6677 | 1226516 | 0.0 | 0.0 | false |
| 21173 | ABI.BR | 2021-01-21 | 56.2 | 56.55 | 55.31 | 55.31 | 51.7968 | 1404283 | 0.0 | 0.0 | false |
| 21174 | ABI.BR | 2021-01-22 | 55.28 | 55.28 | 54.12 | 54.78 | 51.3005 | 1557287 | 0.0 | 0.0 | false |
ohlcv_pl.sample(5, seed=42)| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 11529 | SAN.MC | 2024-09-18 | 4.511 | 4.5455 | 4.5065 | 4.5085 | 4.2785 | 16487238 | 0.0 | 0.0 | false |
| 35604 | CS.PA | 2025-10-14 | 39.34 | 40.27 | 39.25 | 40.18 | 40.18 | 3511125 | 0.0 | 0.0 | false |
| 63666 | WKL.AS | 2022-01-05 | 102.2 | 102.65 | 101.25 | 101.8 | 95.254 | 230509 | 0.0 | 0.0 | false |
| 33136 | PRX.AS | 2021-05-03 | 41.3654 | 41.737 | 41.0718 | 41.3746 | 40.8581 | 2177525 | 0.0 | 0.0 | false |
| 19417 | SAF.PA | 2024-07-12 | 204.2 | 204.8 | 201.3 | 204.8 | 202.5157 | 496739 | 0.0 | 0.0 | false |
unique / drop_duplicates / drop_nulls / dropna
# Pandas — unique tickers
ohlcv_pd["symbol"].drop_duplicates().head(10)| symbol | |
|---|---|
| 0 | ABI.BR |
| 1331 | AD.AS |
| 2662 | ADS.DE |
| 3986 | ADYEN.AS |
| 5317 | AI.PA |
| 6648 | AIR.PA |
| 7979 | ALV.DE |
| 9303 | ARGX.BR |
| 10634 | ASML.AS |
| 11965 | BAS.DE |
# Pandas — drop_duplicates on subset
ohlcv_pd.drop_duplicates(subset=["symbol"]).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.1500 | 58.8500 | 56.7800 | 57.2100 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1331 | 59438 | AD.AS | 2021-01-04 | 23.3800 | 23.8300 | 23.3800 | 23.7900 | 19.9339 | 3526165 | 0.0 | 0.0 | False |
| 2662 | 62088 | ADS.DE | 2021-01-04 | 300.0000 | 300.5000 | 293.0000 | 295.4000 | 282.2904 | 440364 | 0.0 | 0.0 | False |
| 3986 | 60763 | ADYEN.AS | 2021-01-04 | 1900.0000 | 1921.5000 | 1856.0000 | 1859.5000 | 1859.5000 | 99408 | 0.0 | 0.0 | False |
| 5317 | 27773 | AI.PA | 2021-01-04 | 112.3554 | 113.6364 | 111.9835 | 112.7686 | 102.9625 | 917982 | 0.0 | 0.0 | False |
# Pandas — dropna
scores_pd.dropna(subset=["ev_ebitda_zscore"]).head()| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 168 | euro_stoxx_50 | DTE.DE | 2026-03-04 | Communication Services | 0.326587 | 0.387463 | 0.379532 | -0.127867 | 0.241429 | 24 | -0.205598 | 1.120650 | 1.112416 | 0.055524 | 0.685752 | 8 | 0.121212 | 1.33333 | False | 0.617835 | 10 | 0.515005 | 2 | 2026-03-04 22:40:25.489180 | 30.838000 | 28.554556 | 164294311936 | 0.032159 | DEUTSCHE TELEKOM AG | Germany | 33.000 | 0.011649 | -0.019608 | 0.193059 | EUR |
| 2 | 174 | euro_stoxx_50 | IFX.DE | 2026-03-04 | Technology | 0.509398 | 0.637215 | 0.677068 | -0.696662 | 0.281755 | 22 | 0.000965 | 1.048244 | 1.198626 | 0.088845 | 0.675764 | 9 | 0.126408 | 1.37500 | False | 0.579187 | 11 | 0.512235 | 3 | 2026-03-04 22:40:25.489180 | 43.480333 | 38.855556 | 57222533120 | 0.011201 | INFINEON TECHNOLOGIES AG | Germany | 43.945 | 0.054343 | -0.066490 | 0.164723 | EUR |
| 3 | 172 | euro_stoxx_50 | ENR.DE | 2026-03-04 | Industrials | -0.902738 | -0.743338 | -1.693212 | -1.326075 | -1.166341 | 46 | 1.645455 | 1.137007 | 1.474095 | 0.051850 | 2.541889 | 1 | 0.075269 | 1.80000 | False | -0.123264 | 29 | 0.417428 | 4 | 2026-03-04 22:40:25.489180 | 155.675000 | 129.122000 | 139207262208 | 0.027249 | Siemens Energy AG | Germany | 162.750 | 0.047297 | -0.039256 | 0.351744 | EUR |
| 4 | 149 | euro_stoxx_50 | ABI.BR | 2026-03-04 | Consumer Defensive | 0.474084 | 0.844075 | 0.552739 | -0.975005 | 0.223973 | 25 | -0.029791 | 1.058542 | 1.142783 | 0.063063 | 0.651891 | 10 | 0.186198 | 1.69231 | False | 0.344755 | 17 | 0.406873 | 5 | 2026-03-04 22:40:25.489180 | 64.342000 | 57.869333 | 125566156800 | 0.024579 | AB INBEV | Belgium | 64.480 | -0.017073 | -0.040762 | 0.174499 | EUR |
| 5 | 196 | euro_stoxx_50 | VOW.DE | 2026-03-04 | Consumer Cyclical | 1.166221 | 0.940273 | 0.379830 | 1.528891 | 1.003804 | 2 | -0.292748 | 0.929392 | 0.969628 | 0.180805 | -0.411969 | 37 | 0.297071 | NaN | False | 0.555357 | 12 | 0.382397 | 6 | 2026-03-04 22:40:25.489180 | 102.286667 | 101.225556 | 47923826688 | 0.009381 | VOLKSWAGEN AG | Germany | 95.600 | 0.013786 | -0.048756 | -0.090390 | EUR |
# Polars — unique
ohlcv_pl.select("symbol").unique().head(10)| symbol |
|---|
| str |
| ARGX.BR |
| OR.PA |
| IBE.MC |
| ADYEN.AS |
| ENI.MI |
| BAYN.DE |
| IFX.DE |
| BNP.PA |
| DSY.PA |
| ENEL.MI |
# Polars — unique on subset
ohlcv_pl.unique(subset=["symbol"]).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 17194 | ENR.DE | 2021-01-04 | 30.62 | 31.2 | 29.91 | 30.13 | 29.8481 | 1693265 | 0.0 | 0.0 | false |
| 1 | ASML.AS | 2021-01-04 | 404.0 | 411.0 | 402.25 | 406.25 | 387.709 | 789502 | 0.0 | 0.0 | false |
| 63406 | WKL.AS | 2021-01-04 | 69.78 | 71.3 | 69.76 | 70.86 | 65.1785 | 516176 | 0.0 | 0.0 | false |
| 42307 | ENI.MI | 2021-01-04 | 8.604 | 8.756 | 8.397 | 8.448 | 6.0466 | 19734004 | 0.0 | 0.0 | false |
| 19837 | IBE.MC | 2021-01-04 | 11.8 | 11.945 | 11.79 | 11.905 | 9.4733 | 14213672 | 0.0 | 0.0 | false |
# Polars — drop_nulls
scores_pl.drop_nulls(subset=["ev_ebitda_zscore"]).head()| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | str | date | str | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool | f64 | i64 | f64 | i64 | datetime[ns] | f64 | f64 | i64 | f64 | str | str | f64 | f64 | f64 | f64 | str |
| 168 | euro_stoxx_50 | DTE.DE | 2026-03-04 | Communication Services | 0.326587 | 0.387463 | 0.379532 | -0.127867 | 0.241429 | 24 | -0.205598 | 1.12065 | 1.112416 | 0.055524 | 0.685752 | 8 | 0.121212 | 1.33333 | false | 0.617835 | 10 | 0.515005 | 2 | 2026-03-04 22:40:25.489180 | 30.838 | 28.554556 | 164294311936 | 0.032159 | DEUTSCHE TELEKOM AG | Germany | 33.0 | 0.011649 | -0.019608 | 0.193059 | EUR |
| 174 | euro_stoxx_50 | IFX.DE | 2026-03-04 | Technology | 0.509398 | 0.637215 | 0.677068 | -0.696662 | 0.281755 | 22 | 0.000965 | 1.048244 | 1.198626 | 0.088845 | 0.675764 | 9 | 0.126408 | 1.375 | false | 0.579187 | 11 | 0.512235 | 3 | 2026-03-04 22:40:25.489180 | 43.480333 | 38.855556 | 57222533120 | 0.011201 | INFINEON TECHNOLOGIES AG | Germany | 43.945 | 0.054343 | -0.06649 | 0.164723 | EUR |
| 172 | euro_stoxx_50 | ENR.DE | 2026-03-04 | Industrials | -0.902738 | -0.743338 | -1.693212 | -1.326075 | -1.166341 | 46 | 1.645455 | 1.137007 | 1.474095 | 0.05185 | 2.541889 | 1 | 0.075269 | 1.8 | false | -0.123264 | 29 | 0.417428 | 4 | 2026-03-04 22:40:25.489180 | 155.675 | 129.122 | 139207262208 | 0.027249 | Siemens Energy AG | Germany | 162.75 | 0.047297 | -0.039256 | 0.351744 | EUR |
| 149 | euro_stoxx_50 | ABI.BR | 2026-03-04 | Consumer Defensive | 0.474084 | 0.844075 | 0.552739 | -0.975005 | 0.223973 | 25 | -0.029791 | 1.058542 | 1.142783 | 0.063063 | 0.651891 | 10 | 0.186198 | 1.69231 | false | 0.344755 | 17 | 0.406873 | 5 | 2026-03-04 22:40:25.489180 | 64.342 | 57.869333 | 125566156800 | 0.024579 | AB INBEV | Belgium | 64.48 | -0.017073 | -0.040762 | 0.174499 | EUR |
| 196 | euro_stoxx_50 | VOW.DE | 2026-03-04 | Consumer Cyclical | 1.166221 | 0.940273 | 0.37983 | 1.528891 | 1.003804 | 2 | -0.292748 | 0.929392 | 0.969628 | 0.180805 | -0.411969 | 37 | 0.297071 | null | false | 0.555357 | 12 | 0.382397 | 6 | 2026-03-04 22:40:25.489180 | 102.286667 | 101.225556 | 47923826688 | 0.009381 | VOLKSWAGEN AG | Germany | 95.6 | 0.013786 | -0.048756 | -0.09039 | EUR |
Sorting
Pandas | sort_values
Sorts ohlcv_pd by close descending to surface the highest-priced OHLCV rows (Hermès RMS.PA at ~2839), then by ["symbol", "date"] with mixed ascending/descending order to list the most recent date first within each ticker.
ohlcv_pd.sort_values("close", ascending=False).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 51473 | 3708 | RMS.PA | 2025-02-14 | 2926.0 | 2957.0 | 2813.0 | 2839.0 | 2802.9382 | 105651 | 0.0 | 0.0 | False |
| 51472 | 3707 | RMS.PA | 2025-02-13 | 2770.0 | 2816.0 | 2765.0 | 2816.0 | 2780.2302 | 80087 | 0.0 | 0.0 | False |
| 51474 | 3709 | RMS.PA | 2025-02-17 | 2825.0 | 2858.0 | 2803.0 | 2809.0 | 2776.7424 | 53852 | 3.5 | 0.0 | False |
| 51475 | 3710 | RMS.PA | 2025-02-18 | 2816.0 | 2827.0 | 2780.0 | 2806.0 | 2773.7771 | 65469 | 0.0 | 0.0 | False |
| 4150 | 60927 | ADYEN.AS | 2021-08-24 | 2725.0 | 2766.0 | 2711.5 | 2766.0 | 2766.0000 | 61431 | 0.0 | 0.0 | False |
# Multi-column sort
ohlcv_pd.sort_values(["symbol", "date"], ascending=[True, False]).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1330 | 66897 | ABI.BR | 2026-03-12 | 62.64 | 62.96 | 62.08 | 62.76 | 62.76 | 303648 | 0.0 | 0.0 | False |
| 1329 | 66781 | ABI.BR | 2026-03-11 | 62.64 | 63.38 | 62.38 | 62.68 | 62.68 | 1679807 | 0.0 | 0.0 | False |
| 1328 | 66780 | ABI.BR | 2026-03-10 | 62.74 | 63.34 | 62.24 | 63.32 | 63.32 | 1828703 | 0.0 | 0.0 | False |
| 1327 | 66779 | ABI.BR | 2026-03-09 | 61.72 | 62.70 | 61.50 | 62.58 | 62.58 | 1880254 | 0.0 | 0.0 | False |
| 1326 | 64764 | ABI.BR | 2026-03-06 | 63.46 | 63.64 | 62.32 | 63.14 | 63.14 | 2519869 | 0.0 | 0.0 | False |
Polars | sort
Sorts ohlcv_pl by close descending, then by ["symbol", "date"] with mixed directions, and demonstrates sort_by inside a with_columns / over expression to reorder close values chronologically within each symbol partition — a pattern useful in group-aware calculations.
ohlcv_pl.sort("close", descending=True).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 3708 | RMS.PA | 2025-02-14 | 2926.0 | 2957.0 | 2813.0 | 2839.0 | 2802.9382 | 105651 | 0.0 | 0.0 | false |
| 3707 | RMS.PA | 2025-02-13 | 2770.0 | 2816.0 | 2765.0 | 2816.0 | 2780.2302 | 80087 | 0.0 | 0.0 | false |
| 3709 | RMS.PA | 2025-02-17 | 2825.0 | 2858.0 | 2803.0 | 2809.0 | 2776.7424 | 53852 | 3.5 | 0.0 | false |
| 3710 | RMS.PA | 2025-02-18 | 2816.0 | 2827.0 | 2780.0 | 2806.0 | 2773.7771 | 65469 | 0.0 | 0.0 | false |
| 60927 | ADYEN.AS | 2021-08-24 | 2725.0 | 2766.0 | 2711.5 | 2766.0 | 2766.0 | 61431 | 0.0 | 0.0 | false |
# Multi-column sort
ohlcv_pl.sort(["symbol", "date"], descending=[False, True]).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 66897 | ABI.BR | 2026-03-12 | 62.64 | 62.96 | 62.08 | 62.76 | 62.76 | 303648 | 0.0 | 0.0 | false |
| 66781 | ABI.BR | 2026-03-11 | 62.64 | 63.38 | 62.38 | 62.68 | 62.68 | 1679807 | 0.0 | 0.0 | false |
| 66780 | ABI.BR | 2026-03-10 | 62.74 | 63.34 | 62.24 | 63.32 | 63.32 | 1828703 | 0.0 | 0.0 | false |
| 66779 | ABI.BR | 2026-03-09 | 61.72 | 62.7 | 61.5 | 62.58 | 62.58 | 1880254 | 0.0 | 0.0 | false |
| 64764 | ABI.BR | 2026-03-06 | 63.46 | 63.64 | 62.32 | 63.14 | 63.14 | 2519869 | 0.0 | 0.0 | false |
# sort_by inside an expression context (useful in group_by)
ohlcv_pl.with_columns(
pl.col("close").sort_by("date").over("symbol").alias("close_chronological")
).head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | close_chronological |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool | f64 |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false | 57.21 |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false | 57.18 |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false | 58.77 |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 | false | 58.4 |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | false | 57.86 |
Filtering After a Join (Practical Example)
Filter OHLCV data to only include tickers that appear in the dimension table.
# Pandas — semi-join style filter
valid_tickers = dim_pd["symbol"].unique()
ohlcv_pd[ohlcv_pd["symbol"].isin(valid_tickers)].head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | False |
| 1 | 21161 | ABI.BR | 2021-01-05 | 56.90 | 57.98 | 56.75 | 57.18 | 53.5480 | 1382722 | 0.0 | 0.0 | False |
| 2 | 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.0370 | 1370204 | 0.0 | 0.0 | False |
| 3 | 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.40 | 54.6905 | 1469911 | 0.0 | 0.0 | False |
| 4 | 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.40 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | False |
# Polars — semi join
ohlcv_pl.join(dim_pl.select("symbol").unique(), on="symbol", how="semi").head()| id | symbol | date | open | high | low | close | adj_close | volume | dividends | stock_splits | is_filled |
|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | date | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool |
| 21160 | ABI.BR | 2021-01-04 | 58.15 | 58.85 | 56.78 | 57.21 | 53.5761 | 1513937 | 0.0 | 0.0 | false |
| 21161 | ABI.BR | 2021-01-05 | 56.9 | 57.98 | 56.75 | 57.18 | 53.548 | 1382722 | 0.0 | 0.0 | false |
| 21162 | ABI.BR | 2021-01-06 | 57.96 | 58.94 | 57.39 | 58.77 | 55.037 | 1370204 | 0.0 | 0.0 | false |
| 21163 | ABI.BR | 2021-01-07 | 58.68 | 58.86 | 57.88 | 58.4 | 54.6905 | 1469911 | 0.0 | 0.0 | false |
| 21164 | ABI.BR | 2021-01-08 | 58.16 | 58.4 | 57.43 | 57.86 | 54.1848 | 1428681 | 0.0 | 0.0 | false |
Filtering scores_daily — Practical Examples
scores_pd.head(3)| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.261140 | NaN | 2.388962 | 1.521163 | 1 | 0.016123 | 1.009090 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | False | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085000 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.320 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 1 | 168 | euro_stoxx_50 | DTE.DE | 2026-03-04 | Communication Services | 0.326587 | 0.387463 | 0.379532 | -0.127867 | 0.241429 | 24 | -0.205598 | 1.120650 | 1.112416 | 0.055524 | 0.685752 | 8 | 0.121212 | 1.33333 | False | 0.617835 | 10 | 0.515005 | 2 | 2026-03-04 22:40:25.489180 | 30.838000 | 28.554556 | 164294311936 | 0.032159 | DEUTSCHE TELEKOM AG | Germany | 33.000 | 0.011649 | -0.019608 | 0.193059 | EUR |
| 2 | 174 | euro_stoxx_50 | IFX.DE | 2026-03-04 | Technology | 0.509398 | 0.637215 | 0.677068 | -0.696662 | 0.281755 | 22 | 0.000965 | 1.048244 | 1.198626 | 0.088845 | 0.675764 | 9 | 0.126408 | 1.37500 | False | 0.579187 | 11 | 0.512235 | 3 | 2026-03-04 22:40:25.489180 | 43.480333 | 38.855556 | 57222533120 | 0.011201 | INFINEON TECHNOLOGIES AG | Germany | 43.945 | 0.054343 | -0.066490 | 0.164723 | EUR |
# Pandas — top scores
scores_pd[scores_pd["relative_value_score"] > 0.8].head()| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.261140 | NaN | 2.388962 | 1.521163 | 1 | 0.016123 | 1.009090 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | False | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085000 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.32 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 5 | 196 | euro_stoxx_50 | VOW.DE | 2026-03-04 | Consumer Cyclical | 1.166221 | 0.940273 | 0.379830 | 1.528891 | 1.003804 | 2 | -0.292748 | 0.929392 | 0.969628 | 0.180805 | -0.411969 | 37 | 0.297071 | NaN | False | 0.555357 | 12 | 0.382397 | 6 | 2026-03-04 22:40:25.489180 | 102.286667 | 101.225556 | 47923826688 | 0.009381 | VOLKSWAGEN AG | Germany | 95.60 | 0.013786 | -0.048756 | -0.090390 | EUR |
| 7 | 166 | euro_stoxx_50 | DG.PA | 2026-03-04 | Industrials | 0.778573 | 0.850985 | 0.928797 | 1.146268 | 0.926156 | 3 | -0.031697 | 1.064353 | 1.095809 | 0.062871 | 0.595820 | 11 | 0.043608 | 2.04762 | False | -0.538059 | 34 | 0.327972 | 8 | 2026-03-04 22:40:25.489180 | 131.013333 | 123.067778 | 74446422016 | 0.014572 | VINCI | France | 134.15 | 0.006754 | -0.054283 | 0.117451 | EUR |
| 18 | 191 | euro_stoxx_50 | SGO.PA | 2026-03-04 | Industrials | 1.060288 | 0.991397 | 1.009773 | 0.546912 | 0.902092 | 4 | -0.390177 | 0.899452 | 0.848740 | 0.276512 | -0.946419 | 43 | 0.360809 | 1.94737 | True | 0.530945 | 13 | 0.162206 | 19 | 2026-03-04 22:40:25.489180 | 86.202000 | 85.090222 | 38256795648 | 0.007488 | SAINT GOBAIN | France | 77.16 | -0.011783 | -0.121185 | -0.112695 | EUR |
| 19 | 189 | euro_stoxx_50 | SAN.PA | 2026-03-04 | Healthcare | 0.799367 | 0.646615 | 0.714066 | 1.090175 | 0.812556 | 7 | -0.432698 | 0.982603 | 0.949547 | 0.285444 | -0.592341 | 39 | 0.262148 | 2.00000 | True | 0.170637 | 20 | 0.130284 | 20 | 2026-03-04 22:40:25.489180 | 79.889667 | 82.906444 | 95673352192 | 0.018727 | SANOFI | France | 79.23 | -0.007889 | -0.019309 | -0.042191 | EUR |
# Polars — top scores
scores_pl.filter(pl.col("relative_value_score") > 0.8).head()| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | str | date | str | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool | f64 | i64 | f64 | i64 | datetime[ns] | f64 | f64 | i64 | f64 | str | str | f64 | f64 | f64 | f64 | str |
| 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.26114 | null | 2.388962 | 1.521163 | 1 | 0.016123 | 1.00909 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | false | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.32 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 196 | euro_stoxx_50 | VOW.DE | 2026-03-04 | Consumer Cyclical | 1.166221 | 0.940273 | 0.37983 | 1.528891 | 1.003804 | 2 | -0.292748 | 0.929392 | 0.969628 | 0.180805 | -0.411969 | 37 | 0.297071 | null | false | 0.555357 | 12 | 0.382397 | 6 | 2026-03-04 22:40:25.489180 | 102.286667 | 101.225556 | 47923826688 | 0.009381 | VOLKSWAGEN AG | Germany | 95.6 | 0.013786 | -0.048756 | -0.09039 | EUR |
| 166 | euro_stoxx_50 | DG.PA | 2026-03-04 | Industrials | 0.778573 | 0.850985 | 0.928797 | 1.146268 | 0.926156 | 3 | -0.031697 | 1.064353 | 1.095809 | 0.062871 | 0.59582 | 11 | 0.043608 | 2.04762 | false | -0.538059 | 34 | 0.327972 | 8 | 2026-03-04 22:40:25.489180 | 131.013333 | 123.067778 | 74446422016 | 0.014572 | VINCI | France | 134.15 | 0.006754 | -0.054283 | 0.117451 | EUR |
| 191 | euro_stoxx_50 | SGO.PA | 2026-03-04 | Industrials | 1.060288 | 0.991397 | 1.009773 | 0.546912 | 0.902092 | 4 | -0.390177 | 0.899452 | 0.84874 | 0.276512 | -0.946419 | 43 | 0.360809 | 1.94737 | true | 0.530945 | 13 | 0.162206 | 19 | 2026-03-04 22:40:25.489180 | 86.202 | 85.090222 | 38256795648 | 0.007488 | SAINT GOBAIN | France | 77.16 | -0.011783 | -0.121185 | -0.112695 | EUR |
| 189 | euro_stoxx_50 | SAN.PA | 2026-03-04 | Healthcare | 0.799367 | 0.646615 | 0.714066 | 1.090175 | 0.812556 | 7 | -0.432698 | 0.982603 | 0.949547 | 0.285444 | -0.592341 | 39 | 0.262148 | 2.0 | true | 0.170637 | 20 | 0.130284 | 20 | 2026-03-04 22:40:25.489180 | 79.889667 | 82.906444 | 95673352192 | 0.018727 | SANOFI | France | 79.23 | -0.007889 | -0.019309 | -0.042191 | EUR |
# Polars — chain multiple filters
scores_pl.filter(
pl.col("relative_value_score").is_not_null(),
pl.col("relative_value_score") > 0.5,
).head()| id | _index | symbol | score_date | sector | pe_zscore | pb_zscore | ev_ebitda_zscore | yield_zscore | relative_value_score | relative_value_rank | relative_strength | sma_50_ratio | sma_200_ratio | dist_from_52w_high | momentum_score | momentum_rank | implied_upside | recommendation_mean | price_falling_analysts_bullish | sentiment_score | sentiment_rank | composite_score | composite_rank | _scored_at | sma_30_close | sma_90_close | market_cap | index_weight | short_name | country | current_price | day_change_pct | five_day_change_pct | ytd_change_pct | currency |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| i64 | str | str | date | str | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | f64 | f64 | f64 | i64 | f64 | f64 | bool | f64 | i64 | f64 | i64 | datetime[ns] | f64 | f64 | i64 | f64 | str | str | f64 | f64 | f64 | f64 | str |
| 163 | euro_stoxx_50 | BNP.PA | 2026-03-04 | Financial Services | 0.913389 | 1.26114 | null | 2.388962 | 1.521163 | 1 | 0.016123 | 1.00909 | 1.130264 | 0.082486 | 0.477966 | 16 | 0.153157 | 1.84211 | false | 0.052711 | 25 | 0.683947 | 1 | 2026-03-04 22:40:25.489180 | 92.085 | 81.181889 | 99751215104 | 0.019525 | BNP PARIBAS ACT.A | France | 89.32 | 0.011437 | -0.073156 | 0.105582 | EUR |
| 196 | euro_stoxx_50 | VOW.DE | 2026-03-04 | Consumer Cyclical | 1.166221 | 0.940273 | 0.37983 | 1.528891 | 1.003804 | 2 | -0.292748 | 0.929392 | 0.969628 | 0.180805 | -0.411969 | 37 | 0.297071 | null | false | 0.555357 | 12 | 0.382397 | 6 | 2026-03-04 22:40:25.489180 | 102.286667 | 101.225556 | 47923826688 | 0.009381 | VOLKSWAGEN AG | Germany | 95.6 | 0.013786 | -0.048756 | -0.09039 | EUR |
| 194 | euro_stoxx_50 | TTE.PA | 2026-03-04 | Energy | 0.691106 | 0.609377 | 0.44961 | 0.731948 | 0.62051 | 12 | 0.0498 | 1.109161 | 1.212503 | 0.08411 | 0.919444 | 5 | 0.041131 | 2.04545 | false | -0.542576 | 35 | 0.332459 | 7 | 2026-03-04 22:40:25.489180 | 63.603 | 58.231667 | 142003961856 | 0.027796 | TOTALENERGIES | France | 66.86 | -0.018209 | -0.00757 | 0.202734 | EUR |
| 166 | euro_stoxx_50 | DG.PA | 2026-03-04 | Industrials | 0.778573 | 0.850985 | 0.928797 | 1.146268 | 0.926156 | 3 | -0.031697 | 1.064353 | 1.095809 | 0.062871 | 0.59582 | 11 | 0.043608 | 2.04762 | false | -0.538059 | 34 | 0.327972 | 8 | 2026-03-04 22:40:25.489180 | 131.013333 | 123.067778 | 74446422016 | 0.014572 | VINCI | France | 134.15 | 0.006754 | -0.054283 | 0.117451 | EUR |
| 150 | euro_stoxx_50 | AD.AS | 2026-03-04 | Consumer Defensive | 0.758806 | 0.330473 | 0.741401 | 1.0574 | 0.72202 | 9 | 0.035485 | 1.155305 | 1.170693 | 0.008379 | 1.135547 | 4 | -0.014969 | 2.29412 | false | -1.03108 | 44 | 0.275495 | 12 | 2026-03-04 22:40:25.489180 | 37.249 | 35.750667 | 36734455808 | 0.00719 | KONINKLIJKE AHOLD DELHAIZE N.V… | Netherlands | 41.42 | 0.019946 | 0.007786 | 0.187841 | EUR |
Comparison Table | Filtering Rows
comparison = r"""
| Operation | Pandas | Polars |
|:---------------------------------|:-------------------------------------------------|:-------------------------------------------------|
| Single boolean filter | `df[df["col"] > x]` | `df.filter(pl.col("col") > x)` |
| `.loc` with condition | `df.loc[mask]` | `df.filter(mask_expr)` |
| AND / OR / NOT | `(c1) & (c2)`, `(c1) \| (c2)`, `~c` | Same operators on expressions |
| `query()` | `df.query("col > 5")` | N/A — use `filter` expressions |
| `isin` / `is_in` | `df[df["col"].isin(lst)]` | `df.filter(pl.col("col").is_in(lst))` |
| `between` / `is_between` | `df[df["col"].between(a, b)]` | `df.filter(pl.col("col").is_between(a, b))` |
| Null check | `df[df["col"].isna()]` / `.notna()` | `filter(pl.col("col").is_null())` / `.is_not_null()` |
| `where` / `mask` | `s.where(cond)` / `s.mask(cond)` | `when(cond).then(val).otherwise(alt)` |
| String filter (`startswith`) | `df[df["c"].str.startswith("X")]` | `filter(pl.col("c").str.starts_with("X"))` |
| Datetime filter (month) | `df[df["d"].dt.month == 1]` | `filter(pl.col("d").dt.month() == 1)` |
| `head` / `tail` | `df.head(n)` / `df.tail(n)` | Same |
| Slice | `df.iloc[a:b]` | `df.slice(offset, length)` |
| Sample | `df.sample(n, random_state=42)` | `df.sample(n, seed=42)` |
| Unique rows | `df.drop_duplicates(subset=…)` | `df.unique(subset=…)` |
| Drop nulls | `df.dropna(subset=…)` | `df.drop_nulls(subset=…)` |
| Sort | `df.sort_values("col", ascending=False)` | `df.sort("col", descending=True)` |
| Multi-col sort | `sort_values(["a","b"], ascending=[T,F])` | `sort(["a","b"], descending=[F,T])` |
| Expression-level sort | N/A | `pl.col("c").sort_by("d").over("g")` |
| Semi-join filter | `df[df["k"].isin(other["k"])]` | `df.join(other, on="k", how="semi")` |
"""
display(Markdown(comparison))| Operation | Pandas | Polars |
|---|---|---|
| Single boolean filter | df[df["col"] > x] | df.filter(pl.col("col") > x) |
.loc with condition | df.loc[mask] | df.filter(mask_expr) |
| AND / OR / NOT | (c1) & (c2), (c1) | (c2), ~c | Same operators on expressions |
query() | df.query("col > 5") | N/A — use filter expressions |
isin / is_in | df[df["col"].isin(lst)] | df.filter(pl.col("col").is_in(lst)) |
between / is_between | df[df["col"].between(a, b)] | df.filter(pl.col("col").is_between(a, b)) |
| Null check | df[df["col"].isna()] / .notna() | filter(pl.col("col").is_null()) / .is_not_null() |
where / mask | s.where(cond) / s.mask(cond) | when(cond).then(val).otherwise(alt) |
String filter (startswith) | df[df["c"].str.startswith("X")] | filter(pl.col("c").str.starts_with("X")) |
| Datetime filter (month) | df[df["d"].dt.month == 1] | filter(pl.col("d").dt.month() == 1) |
head / tail | df.head(n) / df.tail(n) | Same |
| Slice | df.iloc[a:b] | df.slice(offset, length) |
| Sample | df.sample(n, random_state=42) | df.sample(n, seed=42) |
| Unique rows | df.drop_duplicates(subset=…) | df.unique(subset=…) |
| Drop nulls | df.dropna(subset=…) | df.drop_nulls(subset=…) |
| Sort | df.sort_values("col", ascending=False) | df.sort("col", descending=True) |
| Multi-col sort | sort_values(["a","b"], ascending=[T,F]) | sort(["a","b"], descending=[F,T]) |
| Expression-level sort | N/A | pl.col("c").sort_by("d").over("g") |
| Semi-join filter | df[df["k"].isin(other["k"])] | df.join(other, on="k", how="semi") |
Common Traps and Safe Patterns
Pandas filter() vs Polars filter()
The same method name means different things across libraries
In Pandas,
df.filter(items=[...])selects columns by label. In Polars,df.filter(expr)selects rows by expression. Reusing the name without checking the API leads to quiet, wrong results.
Use the row-selection API that matches the library
In Pandas, filter rows with boolean masks or
.loc[...]. In Polars, use.filter(...)with expressions such aspl.col("close") > 50.
Parenthesize Boolean Conditions
Unparenthesized boolean expressions change operator precedence
df[df["a"] > 5 & df["b"] < 10]is parsed incorrectly because&binds more tightly than the comparison operators. The same precedence trap exists in both Pandas and Polars expression code.
Wrap each condition and combine them with bitwise operators
Write
(cond1) & (cond2)and(cond1) | (cond2)explicitly. That makes the intent unambiguous and keeps the filter semantics correct in both libraries.
Nulls Need Null Predicates
Equality filters never match missing values
In Pandas,
NaN != NaN, sodf["col"] == valuecannot recover null rows. The same conceptual rule applies elsewhere: missing values need dedicated null predicates, not equality comparisons.
Use
.isna()or.is_null()when the target is missing dataReach for
df["col"].isna()in Pandas andpl.col("col").is_null()in Polars whenever the filtering condition is “missing” rather than “equal to a concrete value”.
Label Slices and Positional Slices Behave Differently
.locand.ilocdo not share the same endpoint rules
.loc["a":"c"]includes both labels, while.iloc[0:3]excludes the right endpoint. Swapping one for the other without adjusting the slice leads to off-by-one errors.
Decide first whether the slice is label-based or position-based
Use
.locwhen the boundary values are labels you want included, and.ilocwhen you mean Python-style positional slicing. Treat them as different tools, not as interchangeable spellings.
Deduplication Depends on Existing Order
keep=\"first\"is only meaningful after deterministic sorting
drop_duplicates(..., keep="first")and similar “first row wins” patterns are order-sensitive. If the incoming frame is unsorted, the surviving row is arbitrary from a business perspective.
Sort first when deduplication must be reproducible
Establish the winning row explicitly with a sort on timestamp, priority, or another business key before you drop duplicates or keep the first occurrence.
Sampling Without a Seed Breaks Reproducibility
Unseeded samples change on every run
Random sampling without
random_state=in Pandas orseed=in Polars returns different rows every time. That makes notebooks, tests, and benchmarks harder to compare or debug.
Set the sampling seed whenever the result must be repeatable
Pass
random_state=in Pandas andseed=in Polars for any sample that will be inspected, committed, tested, or compared across runs.
Python Explore, Select and Filter Recommendations
- Profile before transforming — run
describe(),null_count(),value_counts(), andn_unique()on every dataset before writing any transformation logic. This takes seconds and prevents hours of debugging. - Filter early, select early — push filters and column selection as close to the data source as possible. In Polars lazy mode, this enables predicate pushdown and projection pushdown.
- Use Polars selectors for type-based selection —
cs.numeric(),cs.string(),cs.temporal()are safer than hard-coding column names, which break when schemas change. - Use
validate=on joins before filtering — if your filter depends on a join result, validate the join cardinality first (validate="one_to_one"or"many_to_one") to catch unexpected row multiplication. - Prefer
is_in()over chained|conditions —df.filter(pl.col("ticker").is_in(["A", "B", "C"]))is cleaner and faster than(col == "A") | (col == "B") | (col == "C"). - Always pass
dropna=Falsein Pandasvalue_counts()— the default drops NaN, hiding the null count from your cardinality analysis. - Sort before
head()/tail()in unsorted data —head()on an unsorted DataFrame returns arbitrary rows, not the “first” in any meaningful order.
Troubleshooting and failure modes
| Symptom | Likely cause | Fix |
|---|---|---|
KeyError on column selection | Column name has whitespace or case mismatch | df.columns = df.columns.str.strip() then verify exact names |
| Filter returns empty DataFrame unexpectedly | Filter condition is too restrictive, or NaN rows excluded silently | Check value_counts(dropna=False) on the filter column |
TypeError: Cannot perform 'rand_' with... | Missing parentheses around boolean conditions | Wrap each condition in (): (cond1) & (cond2) |
.loc returns a Series instead of a DataFrame | Selecting a single column with .loc[:, "col"] | Use .loc[:, ["col"]] (list) to get a DataFrame |
ColumnNotFoundError in Polars | Column name does not exist or was renamed upstream | Check df.columns or df.schema before the failing operation |
describe() shows unexpected count < total rows | Non-numeric columns excluded by default (Pandas) | Use describe(include="all") or target specific dtypes |
sample() gives different results across runs | No seed set | Pass random_state=42 (Pandas) or seed=42 (Polars) |
unique() returns unordered values | Both libraries return unique values in arbitrary order | Chain .sort() after unique() if order matters |
Polars filter() returns all rows unchanged | Expression always evaluates to True (e.g., comparing wrong column) | Print the boolean expression separately to verify: df.select(expr) |
isin() returns all False | List values don’t match column dtype (e.g., string “1” vs integer 1) | Ensure the list values match the column dtype exactly |