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


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)

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
321163ABI.BR2021-01-0758.6858.8657.8858.4054.690514699110.00.0False
421164ABI.BR2021-01-0858.1658.4057.4357.8654.184814286810.00.0False
display(Markdown("**Last 5 rows (tail):**"))
display(ohlcv_pd.tail())

Last 5 rows (tail)

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
6635064828WKL.AS2026-03-0669.0269.3667.8268.5268.5211437290.00.0False
6635166875WKL.AS2026-03-0968.7869.1667.6468.6468.648415030.00.0False
6635266876WKL.AS2026-03-1068.8069.1666.3467.1667.1613556450.00.0False
6635366877WKL.AS2026-03-1167.5069.6067.0267.2267.2211425310.00.0False
6635466929WKL.AS2026-03-1267.0067.5466.2867.3267.322103790.00.0False

Pandas | sample()

display(Markdown("**Random sample of 5 rows:**"))
display(ohlcv_pd.sample(5, random_state=42))

Random sample of 5 rows

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
4305338920MUV2.DE2023-03-29320.0000322.600318.400322.4000290.32001958090.00.0False
562095772SAP.DE2022-11-0395.920096.34095.08095.510091.905214249730.00.0False
5351511015SAN.MC2022-09-152.59752.6862.5972.67652.3373701583490.00.0False
649828954AI.PA2025-08-12173.3200174.440172.800173.6800173.68004156520.00.0False
6352724956UCG.MI2025-07-0756.460057.35056.44057.350056.046847055670.00.0False

Polars | head() and tail()

display(Markdown("**First 5 rows (head):**"))
display(ohlcv_pl.head())

First 5 rows (head)

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
21163ABI.BR2021-01-0758.6858.8657.8858.454.690514699110.00.0false
21164ABI.BR2021-01-0858.1658.457.4357.8654.184814286810.00.0false
display(Markdown("**Last 5 rows (tail):**"))
display(ohlcv_pl.tail())

Last 5 rows (tail)

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
64828WKL.AS2026-03-0669.0269.3667.8268.5268.5211437290.00.0false
66875WKL.AS2026-03-0968.7869.1667.6468.6468.648415030.00.0false
66876WKL.AS2026-03-1068.869.1666.3467.1667.1613556450.00.0false
66877WKL.AS2026-03-1167.569.667.0267.2267.2211425310.00.0false
66929WKL.AS2026-03-1267.067.5466.2867.3267.322103790.00.0false

Polars | sample() and glimpse()

display(Markdown("**Random sample of 5 rows:**"))
display(ohlcv_pl.sample(5, seed=42))

Random sample of 5 rows

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
11529SAN.MC2024-09-184.5114.54554.50654.50854.2785164872380.00.0false
35604CS.PA2025-10-1439.3440.2739.2540.1840.1835111250.00.0false
63666WKL.AS2022-01-05102.2102.65101.25101.895.2542305090.00.0false
33136PRX.AS2021-05-0341.365441.73741.071841.374640.858121775250.00.0false
19417SAF.PA2024-07-12204.2204.8201.3204.8202.51574967390.00.0false
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)

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
21163ABI.BR2021-01-0758.6858.8657.8858.454.690514699110.00.0false
21164ABI.BR2021-01-0858.1658.457.4357.8654.184814286810.00.0false

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

idopenhighlowcloseadj_closevolumedividendsstock_splits
count66355.00000066355.00000066355.00000066355.00000066355.00000066355.0000006.635500e+0466355.00000066355.000000
mean33179.733102197.040520199.364124194.585782197.034900190.4949095.942124e+060.0117570.000172
std19158.201385363.150484367.873829358.011643363.052047359.6353011.615619e+070.2831420.022716
min1.0000001.6010001.6628001.5842001.6066001.2013000.000000e+000.0000000.000000
25%16589.50000029.78995030.09000029.47000029.78745028.1434005.099855e+050.0000000.000000
50%33178.00000070.70000071.40000069.89000070.68000063.1410001.415896e+060.0000000.000000
75%49766.500000185.990000188.000000184.000000186.100000175.2539004.089299e+060.0000000.000000
max66930.0000002926.0000002957.0000002813.0000002839.0000002802.9382003.763915e+0822.5000005.000000
display(Markdown("**Include all dtypes:**"))
display(ohlcv_pd.describe(include="all"))

Include all dtypes

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
count66355.000000663556635566355.00000066355.00000066355.00000066355.00000066355.0000006.635500e+0466355.00000066355.00000066355
uniqueNaN501331NaNNaNNaNNaNNaNNaNNaNNaN2
topNaNABI.BR2026-03-12NaNNaNNaNNaNNaNNaNNaNNaNFalse
freqNaN133150NaNNaNNaNNaNNaNNaNNaNNaN66349
mean33179.733102NaNNaN197.040520199.364124194.585782197.034900190.4949095.942124e+060.0117570.000172NaN
std19158.201385NaNNaN363.150484367.873829358.011643363.052047359.6353011.615619e+070.2831420.022716NaN
min1.000000NaNNaN1.6010001.6628001.5842001.6066001.2013000.000000e+000.0000000.000000NaN
25%16589.500000NaNNaN29.78995030.09000029.47000029.78745028.1434005.099855e+050.0000000.000000NaN
50%33178.000000NaNNaN70.70000071.40000069.89000070.68000063.1410001.415896e+060.0000000.000000NaN
75%49766.500000NaNNaN185.990000188.000000184.000000186.100000175.2539004.089299e+060.0000000.000000NaN
max66930.000000NaNNaN2926.0000002957.0000002813.0000002839.0000002802.9382003.763915e+0822.5000005.000000NaN

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)
ColumnNon-NullDtype
0id66355int64
1symbol66355object
2date66355object
3open66355float64
4high66355float64
5low66355float64
6close66355float64
7adj_close66355float64
8volume66355int64
9dividends66355float64
10stock_splits66355float64
11is_filled66355bool

Pandas | dtypes

display(ohlcv_pd.dtypes)
0
idint64
symbolobject
dateobject
openfloat64
highfloat64
lowfloat64
closefloat64
adj_closefloat64
volumeint64
dividendsfloat64
stock_splitsfloat64
is_filledbool

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)

statisticidsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
strf64strstrf64f64f64f64f64f64f64f64f64
count66355.0663556635566355.066355.066355.066355.066355.066355.066355.066355.066355.0
null_count0.0000.00.00.00.00.00.00.00.00.0
mean33179.733102null2023-08-05 00:56:42.354005197.04052199.364124194.585782197.0349190.4949095.9421e60.0117570.0001720.00009
std19158.201385nullnull363.150484367.873829358.011643363.052047359.6353011.6156e70.2831420.022716null
min1.0ABI.BR2021-01-041.6011.66281.58421.60661.20130.00.00.00.0
25%16590.0null2022-04-2029.7930.0929.4729.789928.1461509991.00.00.0null
50%33178.0null2023-08-0370.771.469.8970.6863.1411.415896e60.00.0null
75%49767.0null2024-11-19186.0188.0184.0186.1175.26094.089463e60.00.0null
max66930.0WKL.AS2026-03-122926.02957.02813.02839.02802.93823.76391539e822.55.01.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.BR1331
AD.AS1331
ADYEN.AS1331
AI.PA1331
AIR.PA1331
ARGX.BR1331
ASML.AS1331
CS.PA1331
DG.PA1331
BN.PA1331

Pandas | nunique() and unique()

display(Markdown("**Number of unique values per column:**"))
display(ohlcv_pd.nunique())

Number of unique values per column

0
id66355
symbol50
date1331
open29671
high31651
low31695
close31505
adj_close57739
volume65199
dividends216
stock_splits6
is_filled2
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

symbolcount
stru32
SAN.PA1331
ADYEN.AS1331
PRX.AS1331
ARGX.BR1331
BN.PA1331
DSY.PA1331
BNP.PA1331
TTE.PA1331
ASML.AS1331
CS.PA1331

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

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
u32u32u32u32u32u32u32u32u32u32u32u32
6635550133129671316513169531505577396519921662
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 both NaN and None. Polars .is_null() detects only Arrow null — NaN is a valid float value in Polars, not a null. Use .is_nan() to detect NaN specifically in Polars float columns.

Pandas | isna() / isnull()

display(Markdown("**Null counts per column:**"))
display(scores_pd.isnull().sum())

Null counts per column

0
id0
_index0
symbol0
score_date0
sector0
pe_zscore3
pb_zscore6
ev_ebitda_zscore71
yield_zscore35
relative_value_score0
relative_value_rank0
relative_strength0
sma_50_ratio0
sma_200_ratio0
dist_from_52w_high0
momentum_score0
momentum_rank0
implied_upside0
recommendation_mean14
price_falling_analysts_bullish0
sentiment_score0
sentiment_rank0
composite_score0
composite_rank0
_scored_at0
sma_30_close0
sma_90_close0
market_cap0
index_weight0
short_name0
country0
current_price0
day_change_pct0
five_day_change_pct0
ytd_change_pct0
currency0
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_zscore0.64
pb_zscore1.29
ev_ebitda_zscore15.24
yield_zscore7.51
recommendation_mean3.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_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
0163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.261140NaN2.3889621.52116310.0161231.0090901.1302640.0824860.477966160.1531571.84211False0.052711250.68394712026-03-04 22:40:25.48918092.08500081.181889997512151040.019525BNP PARIBAS ACT.AFrance89.3200.011437-0.0731560.105582EUR
5196euro_stoxx_50VOW.DE2026-03-04Consumer Cyclical1.1662210.9402730.379831.5288911.0038042-0.2927480.9293920.9696280.180805-0.411969370.297071NaNFalse0.555357120.38239762026-03-04 22:40:25.489180102.286667101.225556479238266880.009381VOLKSWAGEN AGGermany95.6000.013786-0.048756-0.090390EUR
8188euro_stoxx_50SAN.MC2026-03-04Financial Services0.4585990.499398NaN-1.107590-0.049864330.3931970.9538241.1395190.1134990.519307140.2247041.70000False0.448776150.30607392026-03-04 22:40:25.48918010.6049009.9195001459557498880.028570BANCO SANTANDER S.A.Spain9.9820.038818-0.105876-0.008739EUR
15176euro_stoxx_50ISP.MI2026-03-04Financial Services0.3666190.488302NaN0.7233610.52609413-0.0652470.9224920.9908550.119662-0.122626330.2541502.00000False0.146959210.183476162026-03-04 22:40:25.4891805.8349335.769367942683176960.018452INTESA SANPAOLOItaly5.4220.018216-0.067103-0.084276EUR
16195euro_stoxx_50UCG.MI2026-03-04Financial Services0.4525010.355225NaN-0.2606740.182351260.0858670.9508891.0544300.1378620.123670220.2615211.94444False0.240819180.182280172026-03-04 22:40:25.48918073.25333368.9835561030665011200.020174UNICREDITItaly68.7900.027483-0.072161-0.030034EUR

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_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32
000003671350000000001400000000000000000
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_pctsymbol_null_pctscore_date_null_pctsector_null_pctpe_zscore_null_pctpb_zscore_null_pctev_ebitda_zscore_null_pctyield_zscore_null_pctrelative_value_score_null_pctrelative_value_rank_null_pctrelative_strength_null_pctsma_50_ratio_null_pctsma_200_ratio_null_pctdist_from_52w_high_null_pctmomentum_score_null_pctmomentum_rank_null_pctimplied_upside_null_pctrecommendation_mean_null_pctprice_falling_analysts_bullish_null_pctsentiment_score_null_pctsentiment_rank_null_pctcomposite_score_null_pctcomposite_rank_null_pct_scored_at_null_pctsma_30_close_null_pctsma_90_close_null_pctmarket_cap_null_pctindex_weight_null_pctshort_name_null_pctcountry_null_pctcurrent_price_null_pctday_change_pct_null_pctfive_day_change_pct_null_pctytd_change_pct_null_pctcurrency_null_pct
f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64
0.00.00.00.00.00.641.2915.247.510.00.00.00.00.00.00.00.00.03.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.00.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_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
i64strstrdatestrf64f64f64f64f64i64f64f64f64f64f64i64f64f64boolf64i64f64i64datetime[ns]f64f64i64f64strstrf64f64f64f64str
163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.26114null2.3889621.52116310.0161231.009091.1302640.0824860.477966160.1531571.84211false0.052711250.68394712026-03-04 22:40:25.48918092.08581.181889997512151040.019525BNP PARIBAS ACT.AFrance89.320.011437-0.0731560.105582EUR
196euro_stoxx_50VOW.DE2026-03-04Consumer Cyclical1.1662210.9402730.379831.5288911.0038042-0.2927480.9293920.9696280.180805-0.411969370.297071nullfalse0.555357120.38239762026-03-04 22:40:25.489180102.286667101.225556479238266880.009381VOLKSWAGEN AGGermany95.60.013786-0.048756-0.09039EUR
188euro_stoxx_50SAN.MC2026-03-04Financial Services0.4585990.499398null-1.10759-0.049864330.3931970.9538241.1395190.1134990.519307140.2247041.7false0.448776150.30607392026-03-04 22:40:25.48918010.60499.91951459557498880.02857BANCO SANTANDER S.A.Spain9.9820.038818-0.105876-0.008739EUR
176euro_stoxx_50ISP.MI2026-03-04Financial Services0.3666190.488302null0.7233610.52609413-0.0652470.9224920.9908550.119662-0.122626330.254152.0false0.146959210.183476162026-03-04 22:40:25.4891805.8349335.769367942683176960.018452INTESA SANPAOLOItaly5.4220.018216-0.067103-0.084276EUR
195euro_stoxx_50UCG.MI2026-03-04Financial Services0.4525010.355225null-0.2606740.182351260.0858670.9508891.054430.1378620.12367220.2615211.94444false0.240819180.18228172026-03-04 22:40:25.48918073.25333368.9835561030665011200.020174UNICREDITItaly68.790.027483-0.072161-0.030034EUR

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_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
0163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.261140NaN2.3889621.52116310.0161231.0090901.1302640.0824860.477966160.1531571.84211False0.052711250.68394712026-03-04 22:40:25.48918092.08500081.181889997512151040.019525BNP PARIBAS ACT.AFrance89.3200.011437-0.0731560.105582EUR
1168euro_stoxx_50DTE.DE2026-03-04Communication Services0.3265870.3874630.379532-0.1278670.24142924-0.2055981.1206501.1124160.0555240.68575280.1212121.33333False0.617835100.51500522026-03-04 22:40:25.48918030.83800028.5545561642943119360.032159DEUTSCHE TELEKOM AGGermany33.0000.011649-0.0196080.193059EUR
2174euro_stoxx_50IFX.DE2026-03-04Technology0.5093980.6372150.677068-0.6966620.281755220.0009651.0482441.1986260.0888450.67576490.1264081.37500False0.579187110.51223532026-03-04 22:40:25.48918043.48033338.855556572225331200.011201INFINEON TECHNOLOGIES AGGermany43.9450.054343-0.0664900.164723EUR
3172euro_stoxx_50ENR.DE2026-03-04Industrials-0.902738-0.743338-1.693212-1.326075-1.166341461.6454551.1370071.4740950.0518502.54188910.0752691.80000False-0.123264290.41742842026-03-04 22:40:25.489180155.675000129.1220001392072622080.027249Siemens Energy AGGermany162.7500.047297-0.0392560.351744EUR
4149euro_stoxx_50ABI.BR2026-03-04Consumer Defensive0.4740840.8440750.552739-0.9750050.22397325-0.0297911.0585421.1427830.0630630.651891100.1861981.69231False0.344755170.40687352026-03-04 22:40:25.48918064.34200057.8693331255661568000.024579AB INBEVBelgium64.480-0.017073-0.0407620.174499EUR
display(Markdown("**scores_daily — describe:**"))
display(scores_pd.describe(include="all"))

scores_daily — describe

id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
count466.000000466466466466463.000000460.000000395.000000431.000000466.000000466.000000466.000000466.000000466.000000466.000000466.000000466.000000466.000000452.000000466466.000000466.000000466.000000466.000000466466.000000466.0000004.660000e+02466.000000466466466.000000466.000000466.000000466.000000466
uniqueNaN4167310NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN2NaNNaNNaNNaNNaNNaNNaNNaNNaN16716NaNNaNNaNNaN6
topNaNstoxx_asia_50CVX2026-03-12Financial ServicesNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNFalseNaNNaNNaNNaNNaNNaNNaNNaNNaNChevron CorporationUnited StatesNaNNaNNaNNaNUSD
freqNaN150416996NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN401NaNNaNNaNNaNNaNNaNNaNNaNNaN4159NaNNaNNaNNaN167
mean2083.712446NaNNaNNaNNaN0.0113860.0245130.0380460.0519150.03620424.7339060.0479720.9911521.0649810.154695-0.00015724.7682400.1750492.011047NaN-0.00415724.7939910.01063024.7746782026-03-08 09:35:56.5016458242726.9807222568.2258903.030703e+120.021271NaNNaN2721.358232-0.001651-0.0248350.028935NaN
min149.000000NaNNaNNaNNaN-3.244355-3.609853-3.562197-1.765215-3.0691511.000000-0.7581110.7647150.6301630.000686-2.2706821.000000-0.2872051.224490NaN-3.0281901.000000-1.2659251.0000002026-03-04 22:40:25.4891804.9676674.9120001.516982e+100.000102NaNNaN5.120000-0.071643-0.147762-0.326764NaN
25%266.250000NaNNaNNaNNaN-0.544030-0.360416-0.292057-0.680445-0.32724112.000000-0.2071240.9288230.9469010.073301-0.44254212.0000000.0705491.666670NaN-0.63283412.000000-0.17550012.0000002026-03-04 22:40:25.48917990471.67150072.9129449.705579e+100.007191NaNNaN69.900000-0.013312-0.054816-0.083552NaN
50%2387.500000NaNNaNNaNNaN0.2944670.3289870.3098760.0512610.17810024.000000-0.0278790.9847051.0580550.1241070.02980424.0000000.1529671.944440NaN0.02366124.0000000.03193924.0000002026-03-07 03:00:57.486865920206.003000202.1933332.577942e+110.012848NaNNaN203.470000-0.002397-0.0235060.019151NaN
75%3410.750000NaNNaNNaNNaN0.6437420.6171030.6229760.7186980.52351637.0000000.1978731.0460221.1773060.2005600.48843337.0000000.2731512.280000NaN0.52792737.0000000.24382037.0000002026-03-12 12:52:26.509884928856.826917837.6290001.628276e+120.027448NaNNaN799.0575000.0099590.0021310.129549NaN
max3527.000000NaNNaNNaNNaN1.6661772.0191051.6975272.9198891.52116350.0000003.3429941.2330111.9050600.5890012.80674150.0000000.8048174.785710NaN2.16972350.0000001.28714450.0000002026-03-12 12:52:26.50988568837.66666760523.2222224.587752e+130.245721NaNNaN69950.0000000.0918340.1929870.466977NaN
std1340.387660NaNNaNNaNNaN0.8946790.9223490.8752910.9147140.72383814.4572790.4624820.0817330.1824690.1179970.81716614.4721440.1728020.512925NaN0.88600814.4582620.33621014.470685NaN9736.6664508945.9619016.558209e+120.024398NaNNaN9820.2548260.0221180.0465500.151246NaN
display(Markdown("**scores_daily — null counts:**"))
display(scores_pd.isnull().sum())

scores_daily — null counts

0
id0
_index0
symbol0
score_date0
sector0
pe_zscore3
pb_zscore6
ev_ebitda_zscore71
yield_zscore35
relative_value_score0
relative_value_rank0
relative_strength0
sma_50_ratio0
sma_200_ratio0
dist_from_52w_high0
momentum_score0
momentum_rank0
implied_upside0
recommendation_mean14
price_falling_analysts_bullish0
sentiment_score0
sentiment_rank0
composite_score0
composite_rank0
_scored_at0
sma_30_close0
sma_90_close0
market_cap0
index_weight0
short_name0
country0
current_price0
day_change_pct0
five_day_change_pct0
ytd_change_pct0
currency0

Polars | quick profile

display(Markdown("**scores_daily — head (Polars):**"))
display(scores_pl.head())

scores_daily — head (Polars)

id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
i64strstrdatestrf64f64f64f64f64i64f64f64f64f64f64i64f64f64boolf64i64f64i64datetime[ns]f64f64i64f64strstrf64f64f64f64str
163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.26114null2.3889621.52116310.0161231.009091.1302640.0824860.477966160.1531571.84211false0.052711250.68394712026-03-04 22:40:25.48918092.08581.181889997512151040.019525BNP PARIBAS ACT.AFrance89.320.011437-0.0731560.105582EUR
168euro_stoxx_50DTE.DE2026-03-04Communication Services0.3265870.3874630.379532-0.1278670.24142924-0.2055981.120651.1124160.0555240.68575280.1212121.33333false0.617835100.51500522026-03-04 22:40:25.48918030.83828.5545561642943119360.032159DEUTSCHE TELEKOM AGGermany33.00.011649-0.0196080.193059EUR
174euro_stoxx_50IFX.DE2026-03-04Technology0.5093980.6372150.677068-0.6966620.281755220.0009651.0482441.1986260.0888450.67576490.1264081.375false0.579187110.51223532026-03-04 22:40:25.48918043.48033338.855556572225331200.011201INFINEON TECHNOLOGIES AGGermany43.9450.054343-0.066490.164723EUR
172euro_stoxx_50ENR.DE2026-03-04Industrials-0.902738-0.743338-1.693212-1.326075-1.166341461.6454551.1370071.4740950.051852.54188910.0752691.8false-0.123264290.41742842026-03-04 22:40:25.489180155.675129.1221392072622080.027249Siemens Energy AGGermany162.750.047297-0.0392560.351744EUR
149euro_stoxx_50ABI.BR2026-03-04Consumer Defensive0.4740840.8440750.552739-0.9750050.22397325-0.0297911.0585421.1427830.0630630.651891100.1861981.69231false0.344755170.40687352026-03-04 22:40:25.48918064.34257.8693331255661568000.024579AB INBEVBelgium64.48-0.017073-0.0407620.174499EUR
display(Markdown("**scores_daily — describe (Polars):**"))
display(scores_pl.describe())

scores_daily — describe (Polars)

statisticid_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
strf64strstrstrstrf64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64f64strf64f64f64f64strstrf64f64f64f64str
count466.0466466466466463.0460.0395.0431.0466.0466.0466.0466.0466.0466.0466.0466.0466.0452.0466.0466.0466.0466.0466.0466466.0466.0466.0466.0466466466.0466.0466.0466.0466
null_count0.000003.06.071.035.00.00.00.00.00.00.00.00.00.014.00.00.00.00.00.000.00.00.00.0000.00.00.00.00
mean2083.712446nullnull2026-03-07 20:48:24.721030null0.0113860.0245130.0380460.0519150.03620424.7339060.0479720.9911521.0649810.154695-0.00015724.768240.1750492.0110470.139485-0.00415724.7939910.0106324.7746782026-03-08 09:35:56.5016462726.9807222568.225893.0307e120.021271nullnull2721.358232-0.001651-0.0248350.028935null
std1340.38766nullnullnullnull0.8946790.9223490.8752910.9147140.72383814.4572790.4624820.0817330.1824690.1179970.81716614.4721440.1728020.512925null0.88600814.4582620.3362114.470685null9736.666458945.9619016.5582e120.024398nullnull9820.2548260.0221180.046550.151246null
min149.0euro_stoxx_500388.HK2026-03-04Basic Materials-3.244355-3.609853-3.562197-1.765215-3.0691511.0-0.7581110.7647150.6301630.000686-2.2706821.0-0.2872051.224490.0-3.028191.0-1.2659251.02026-03-04 22:40:25.4891804.9676674.9121.5170e100.000102AB INBEVAustralia5.12-0.071643-0.147762-0.326764AUD
25%266.0nullnull2026-03-04null-0.542352-0.358338-0.285053-0.678854-0.32739812.0-0.2072950.9286340.946690.073233-0.44307512.00.0703361.66667null-0.63556312.0-0.17600712.02026-03-04 22:40:25.48917971.52772.8442229.7039e100.00719nullnull69.8-0.013336-0.054849-0.083791null
50%2388.0nullnull2026-03-07null0.2944670.329960.3098760.0512610.17935924.0-0.0259680.9847871.0580790.1241810.03118424.00.1531571.94444null0.02415424.00.03234424.02026-03-07 03:00:57.486865207.221667204.3653332.5787e110.013111nullnull204.83-0.002381-0.0231530.019395null
75%3411.0nullnull2026-03-12null0.6455070.6167450.6255870.7233610.52609437.00.1999161.0460951.177340.2007350.48941237.00.2736172.28null0.52875637.00.2448437.02026-03-12 12:52:26.509884900.273667874.9142221.6312e120.027458nullnull821.420.0099630.0021790.12961null
max3527.0stoxx_usa_50XOM2026-03-12Utilities1.6661772.0191051.6975272.9198891.52116350.03.3429941.2330111.905060.5890012.80674150.00.8048174.785711.02.16972350.01.28714450.02026-03-12 12:52:26.50988568837.66666760523.2222224.5878e130.245721adidas AGUnited States69950.00.0918340.1929870.466977USD
display(Markdown("**scores_daily — null counts (Polars):**"))
display(scores_pl.null_count())

scores_daily — null counts (Polars)

id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32u32
000003671350000000001400000000000000000

Exploring the Dimension Table

Head Preview

display(Markdown("**index_dim — Pandas:**"))
display(dim_pd.drop(columns="long_business_summary").head())

index_dim — Pandas

id_indexsymbollong_nameshort_namesectorsector_keyindustryindustry_keycountrycitywebsiteexchangefull_exchange_nameexchange_timezone_nameexchange_timezone_shortcurrencyfinancial_currencyquote_typemarketrange_startprice_data_startvalid_fromvalid_tois_current
01euro_stoxx_50ASML.ASASML Holding N.V.ASML HOLDINGTechnologytechnologySemiconductor Equipment & Materialssemiconductor-equipment-materialsNetherlandsVeldhovenhttps://www.asml.comAMSAmsterdamEurope/AmsterdamCETEUREUREQUITYnl_market1998-07-202021-01-012026-03-04 22:11:36.189862NoneTrue
12euro_stoxx_50MC.PALVMH Moët Hennessy - Louis Vuitton, Société EuropéenneLVMHConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://www.lvmh.comPARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-012026-03-04 22:11:36.189862NoneTrue
23euro_stoxx_50RMS.PAHermès International Société en commandite par actionsHERMES INTLConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://finance.hermes.comPARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-012026-03-04 22:11:36.193940NoneTrue
34euro_stoxx_50OR.PAL'Oréal S.A.L'OREALConsumer Defensiveconsumer-defensiveHousehold & Personal Productshousehold-personal-productsFranceClichyhttps://www.loreal.comPARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-012026-03-04 22:11:36.193940NoneTrue
45euro_stoxx_50SAP.DESAP SESAP SETechnologytechnologySoftware - Applicationsoftware-applicationGermanyWalldorfhttps://www.sap.comGERXETRAEurope/BerlinCETEUREUREQUITYde_market1998-04-092021-01-012026-03-04 22:11:36.193940NoneTrue
display(Markdown("**index_dim — Polars:**"))
display(dim_pl.head())

index_dim — Polars

id_indexsymbollong_nameshort_namesectorsector_keyindustryindustry_keycountrycitywebsitelong_business_summaryexchangefull_exchange_nameexchange_timezone_nameexchange_timezone_shortcurrencyfinancial_currencyquote_typemarketrange_startprice_data_startvalid_fromvalid_tois_current
i64strstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrdatedatedatetime[ns]nullbool
1euro_stoxx_50ASML.ASASML Holding N.V.ASML HOLDINGTechnologytechnologySemiconductor Equipment & Mate…semiconductor-equipment-materi…NetherlandsVeldhovenhttps://www.asml.comASML Holding N.V. provides lit…AMSAmsterdamEurope/AmsterdamCETEUREUREQUITYnl_market1998-07-202021-01-012026-03-04 22:11:36.189862nulltrue
2euro_stoxx_50MC.PALVMH Moët Hennessy - Louis Vui…LVMHConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://www.lvmh.comLVMH Moët Hennessy - Louis Vui…PARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-012026-03-04 22:11:36.189862nulltrue
3euro_stoxx_50RMS.PAHermès International Société e…HERMES INTLConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://finance.hermes.comHermès International Société e…PARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-012026-03-04 22:11:36.193940nulltrue
4euro_stoxx_50OR.PAL'Oréal S.A.L'OREALConsumer Defensiveconsumer-defensiveHousehold & Personal Productshousehold-personal-productsFranceClichyhttps://www.loreal.comL'Oréal S.A., through its subs…PARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-012026-03-04 22:11:36.193940nulltrue
5euro_stoxx_50SAP.DESAP SESAP SETechnologytechnologySoftware - Applicationsoftware-applicationGermanyWalldorfhttps://www.sap.comSAP SE, together with its subs…GERXETRAEurope/BerlinCETEUREUREQUITYde_market1998-04-092021-01-012026-03-04 22:11:36.193940nulltrue

Types and Schema

display(Markdown("**index_dim — dtypes (Pandas):**"))
display(dim_pd.dtypes)

index_dim — dtypes (Pandas)

0
idint64
_indexobject
symbolobject
long_nameobject
short_nameobject
sectorobject
sector_keyobject
industryobject
industry_keyobject
countryobject
cityobject
websiteobject
long_business_summaryobject
exchangeobject
full_exchange_nameobject
exchange_timezone_nameobject
exchange_timezone_shortobject
currencyobject
financial_currencyobject
quote_typeobject
marketobject
range_startobject
price_data_startobject
valid_fromdatetime64[ns]
valid_toobject
is_currentbool
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)

columndtypenull_countnull_pctn_uniquesample
0idint6400.066355[21160, 21161, 21162]
1symbolobject00.050['ABI.BR', 'ABI.BR', 'ABI.BR']
2datedatetime64[ns]00.01331[Timestamp('2021-01-04 00:00:00'), Timestamp('2021-01-05 00:00:00'), Timestamp('2021-01-06 00:00:00')]
3openfloat6400.029671[58.15, 56.9, 57.96]
4highfloat6400.031651[58.85, 57.98, 58.94]
5lowfloat6400.031695[56.78, 56.75, 57.39]
6closefloat6400.031505[57.21, 57.18, 58.77]
7adj_closefloat6400.057739[53.5761, 53.548, 55.037]
8volumeint6400.065199[1513937, 1382722, 1370204]
9dividendsfloat6400.0216[0.0, 0.0, 0.0]
10stock_splitsfloat6400.06[0.0, 0.0, 0.0]
11is_filledbool00.02[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)

columndtypenull_countnull_pctn_uniquesample
strstri64f64i64str
idInt6400.066355[21160, 21161, 21162]
symbolString00.050['ABI.BR', 'ABI.BR', 'ABI.BR']
dateDate00.01331[datetime.date(2021, 1, 4), da…
openFloat6400.029671[58.15, 56.9, 57.96]
highFloat6400.031651[58.85, 57.98, 58.94]
lowFloat6400.031695[56.78, 56.75, 57.39]
closeFloat6400.031505[57.21, 57.18, 58.77]
adj_closeFloat6400.057739[53.5761, 53.548, 55.037]
volumeInt6400.065199[1513937, 1382722, 1370204]
dividendsFloat6400.0216[0.0, 0.0, 0.0]
stock_splitsFloat6400.06[0.0, 0.0, 0.0]
is_filledBoolean00.02[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:

columndtypenull_countnull_pctn_uniquesample
0idint6400.066355[21160, 21161, 21162]
1symbolobject00.050['ABI.BR', 'ABI.BR', 'ABI.BR']
2dateobject00.01331[datetime.date(2021, 1, 4), datetime.date(2021, 1, 5), datetime.date(2021, 1, 6)]
3openfloat6400.029671[58.15, 56.9, 57.96]
4highfloat6400.031651[58.85, 57.98, 58.94]
5lowfloat6400.031695[56.78, 56.75, 57.39]
6closefloat6400.031505[57.21, 57.18, 58.77]
7adj_closefloat6400.057739[53.5761, 53.548, 55.037]
8volumeint6400.065199[1513937, 1382722, 1370204]
9dividendsfloat6400.0216[0.0, 0.0, 0.0]
10stock_splitsfloat6400.06[0.0, 0.0, 0.0]
11is_filledbool00.02[False, False, False]
prof_pl = profile_pl(ohlcv_pl)
display(Markdown("*Polars profile:*"))
display(prof_pl)

Polars profile:

columndtypenull_countnull_pctn_uniquesample
strstri64f64i64str
idInt6400.066355[21160, 21161, 21162]
symbolString00.050['ABI.BR', 'ABI.BR', 'ABI.BR']
dateDate00.01331[datetime.date(2021, 1, 4), da…
openFloat6400.029671[58.15, 56.9, 57.96]
highFloat6400.031651[58.85, 57.98, 58.94]
lowFloat6400.031695[56.78, 56.75, 57.39]
closeFloat6400.031505[57.21, 57.18, 58.77]
adj_closeFloat6400.057739[53.5761, 53.548, 55.037]
volumeInt6400.065199[1513937, 1382722, 1370204]
dividendsFloat6400.0216[0.0, 0.0, 0.0]
stock_splitsFloat6400.06[0.0, 0.0, 0.0]
is_filledBoolean00.02[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:

columndtypenull_countnull_pctn_uniquesample
0idint6400.0169[1, 2, 3]
1_indexobject00.04['euro_stoxx_50', 'euro_stoxx_50', 'euro_stoxx_50']
2symbolobject00.0167['ASML.AS', 'MC.PA', 'RMS.PA']
3long_nameobject00.0166['ASML Holding N.V.', 'LVMH Moët Hennessy - Louis Vuitton, Société Européenne', 'Hermès International Société en commandite par actions']
4short_nameobject00.0167['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:

columndtypenull_countnull_pctn_uniquesample
strstri64f64i64str
idInt6400.0169[1, 2, 3]
_indexString00.04['euro_stoxx_50', 'euro_stoxx_…
symbolString00.0167['ASML.AS', 'MC.PA', 'RMS.PA']
long_nameString00.0166['ASML Holding N.V.', 'LVMH Mo…
short_nameString00.0167['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:

columndtypenull_countnull_pctn_uniquesample
0idint6400.0466[163, 168, 174]
1_indexobject00.04['euro_stoxx_50', 'euro_stoxx_50', 'euro_stoxx_50']
2symbolobject00.0167['BNP.PA', 'DTE.DE', 'IFX.DE']
3score_dateobject00.03[datetime.date(2026, 3, 4), datetime.date(2026, 3, 4), datetime.date(2026, 3, 4)]
4sectorobject00.010['Financial Services', 'Communication Services', 'Technology']
prof_pl = profile_pl(scores_pl)
display(Markdown("*Polars profile:*"))
display(prof_pl.head())

Polars profile:

columndtypenull_countnull_pctn_uniquesample
strstri64f64i64str
idInt6400.0466[163, 168, 174]
_indexString00.04['euro_stoxx_50', 'euro_stoxx_…
symbolString00.0167['BNP.PA', 'DTE.DE', 'IFX.DE']
score_dateDate00.03[datetime.date(2026, 3, 4), da…
sectorString00.010['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))
TaskPandasPolars
First N rowsdf.head(n)df.head(n)
Last N rowsdf.tail(n)df.tail(n)
Random sampledf.sample(n)df.sample(n)
Transposed preview(no built-in)df.glimpse()
Shapedf.shapedf.shape
Describe (numeric)df.describe()df.describe()
Describe (all)df.describe(include="all")df.describe() (all by default)
Column dtypesdf.dtypesdf.dtypes / df.schema
Info summarydf.info()(no direct equivalent)
Value countss.value_counts()s.value_counts()
Unique valuess.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 percentagedf.isnull().mean() * 100pl.all().null_count() / pl.len() * 100
Filter null rowsdf[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 the SettingWithCopyWarning and 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

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
id_indexsymbollong_nameshort_namesectorsector_keyindustryindustry_keycountrycitywebsiteexchangefull_exchange_nameexchange_timezone_nameexchange_timezone_shortcurrencyfinancial_currencyquote_typemarketrange_startprice_data_startvalid_fromvalid_tois_current
01euro_stoxx_50ASML.ASASML Holding N.V.ASML HOLDINGTechnologytechnologySemiconductor Equipment & Materialssemiconductor-equipment-materialsNetherlandsVeldhovenhttps://www.asml.comAMSAmsterdamEurope/AmsterdamCETEUREUREQUITYnl_market1998-07-202021-01-012026-03-04 22:11:36.189862NoneTrue
12euro_stoxx_50MC.PALVMH Moët Hennessy - Louis Vuitton, Société EuropéenneLVMHConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://www.lvmh.comPARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-012026-03-04 22:11:36.189862NoneTrue
23euro_stoxx_50RMS.PAHermès International Société en commandite par actionsHERMES INTLConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://finance.hermes.comPARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-012026-03-04 22:11:36.193940NoneTrue

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
id21160
symbolABI.BR
date2021-01-04
open58.15
high58.85
low56.78
close57.21
adj_close53.5761
volume1513937
dividends0.0
stock_splits0.0
is_filledFalse
# 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

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false

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

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
1021170ABI.BR2021-01-1856.2557.3056.2057.0853.45447302980.00.0False
10021260ABI.BR2021-05-2661.9962.3961.8362.1258.67019401860.00.0False
# 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

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21170ABI.BR2021-01-1856.2557.356.257.0853.45447302980.00.0false
21260ABI.BR2021-05-2661.9962.3961.8362.1258.67019401860.00.0false

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

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
1021170ABI.BR2021-01-1856.2557.3056.2057.0853.45447302980.00.0False
1121171ABI.BR2021-01-1957.1057.2656.2656.3552.770711165700.00.0False
1221172ABI.BR2021-01-2056.3556.7756.0056.2452.667712265160.00.0False
1321173ABI.BR2021-01-2156.2056.5555.3155.3151.796814042830.00.0False
1421174ABI.BR2021-01-2255.2855.2854.1254.7851.300515572870.00.0False
# Polars — slice(offset, length)
display(Markdown("**Polars — rows 10 to 14:**"))
display(ohlcv_pl.slice(10, 5))

Polars | rows 10 to 14

idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21170ABI.BR2021-01-1856.2557.356.257.0853.45447302980.00.0false
21171ABI.BR2021-01-1957.157.2656.2656.3552.770711165700.00.0false
21172ABI.BR2021-01-2056.3556.7756.056.2452.667712265160.00.0false
21173ABI.BR2021-01-2156.256.5555.3155.3151.796814042830.00.0false
21174ABI.BR2021-01-2255.2855.2854.1254.7851.300515572870.00.0false

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_indexlong_nameshort_namesectorsector_keyindustryindustry_keycountrycitywebsiteexchangefull_exchange_nameexchange_timezone_nameexchange_timezone_shortcurrencyfinancial_currencyquote_typemarketrange_startprice_data_startvalid_fromvalid_tois_current
symbol
ASML.AS1euro_stoxx_50ASML Holding N.V.ASML HOLDINGTechnologytechnologySemiconductor Equipment & Materialssemiconductor-equipment-materialsNetherlandsVeldhovenhttps://www.asml.comAMSAmsterdamEurope/AmsterdamCETEUREUREQUITYnl_market1998-07-202021-01-012026-03-04 22:11:36.189862NoneTrue
SAP.DE5euro_stoxx_50SAP SESAP SETechnologytechnologySoftware - Applicationsoftware-applicationGermanyWalldorfhttps://www.sap.comGERXETRAEurope/BerlinCETEUREUREQUITYde_market1998-04-092021-01-012026-03-04 22:11:36.193940NoneTrue
# 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_indexsymbollong_nameshort_namesectorsector_keyindustryindustry_keycountrycitywebsiteexchangefull_exchange_nameexchange_timezone_nameexchange_timezone_shortcurrencyfinancial_currencyquote_typemarketrange_startprice_data_startvalid_fromvalid_tois_current
i64strstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrdatedatedatetime[ns]nullbool
1euro_stoxx_50ASML.ASASML Holding N.V.ASML HOLDINGTechnologytechnologySemiconductor Equipment & Mate…semiconductor-equipment-materi…NetherlandsVeldhovenhttps://www.asml.comAMSAmsterdamEurope/AmsterdamCETEUREUREQUITYnl_market1998-07-202021-01-012026-03-04 22:11:36.189862nulltrue
5euro_stoxx_50SAP.DESAP SESAP SETechnologytechnologySoftware - Applicationsoftware-applicationGermanyWalldorfhttps://www.sap.comGERXETRAEurope/BerlinCETEUREUREQUITYde_market1998-04-092021-01-012026-03-04 22:11:36.193940nulltrue

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

dateopenhighlow
02021-01-0458.1558.8556.78
12021-01-0556.9057.9856.75
22021-01-0657.9658.9457.39
32021-01-0758.6858.8657.88
42021-01-0858.1658.4057.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

dateopenhighlow
datef64f64f64
2021-01-0458.1558.8556.78
2021-01-0556.957.9856.75
2021-01-0657.9658.9457.39
2021-01-0758.6858.8657.88
2021-01-0858.1658.457.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

dateclosevolume
106342021-01-04406.25789502
106352021-01-05406.90798787
106362021-01-06402.85875711
106372021-01-07403.90874780
106382021-01-08416.05975243
# 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

dateclosevolume
datef64i64
2021-01-04406.25789502
2021-01-05406.9798787
2021-01-06402.85875711
2021-01-07403.9874780
2021-01-08416.05975243

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

symbollong_namesectorcountry
0ASML.ASASML Holding N.V.TechnologyNetherlands
1MC.PALVMH Moët Hennessy - Louis Vuitton, Société EuropéenneConsumer CyclicalFrance
2RMS.PAHermès International Société en commandite par actionsConsumer CyclicalFrance
3OR.PAL'Oréal S.A.Consumer DefensiveFrance
4SAP.DESAP SETechnologyGermany
# 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

symbollong_namesectorcountry
strstrstrstr
ASML.ASASML Holding N.V.TechnologyNetherlands
MC.PALVMH Moët Hennessy - Louis Vui…Consumer CyclicalFrance
RMS.PAHermès International Société e…Consumer CyclicalFrance
OR.PAL'Oréal S.A.Consumer DefensiveFrance
SAP.DESAP SETechnologyGermany

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
057.21
157.18
258.77
358.40
457.86
display(ohlcv_pd.close.head())
close
057.21
157.18
258.77
358.40
457.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())
symboldateclose
strdatef64
ABI.BR2021-01-0457.21
ABI.BR2021-01-0557.18
ABI.BR2021-01-0658.77
ABI.BR2021-01-0758.4
ABI.BR2021-01-0857.86
display(ohlcv_pl.select(pl.col("symbol"), pl.col("close")).head())
symbolclose
strf64
ABI.BR57.21
ABI.BR57.18
ABI.BR58.77
ABI.BR58.4
ABI.BR57.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())
symboldateclose
0ABI.BR2021-01-0457.21
1ABI.BR2021-01-0557.18
2ABI.BR2021-01-0658.77
3ABI.BR2021-01-0758.40
4ABI.BR2021-01-0857.86
display(Markdown("**Select specific columns with `loc`:**"))
display(ohlcv_pd.loc[:, ["symbol", "open", "close"]].head())

Select specific columns with loc

symbolopenclose
0ABI.BR58.1557.21
1ABI.BR56.9057.18
2ABI.BR57.9658.77
3ABI.BR58.6858.40
4ABI.BR58.1657.86
display(Markdown("**Slice columns with `loc` (label range):**"))
display(ohlcv_pd.loc[:, "open":"close"].head())

Slice columns with loc (label range)

openhighlowclose
058.1558.8556.7857.21
156.9057.9856.7557.18
257.9658.9457.3958.77
358.6858.8657.8858.40
458.1658.4057.4357.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())
symbolopenhighlowclose
strf64f64f64f64
ABI.BR58.1558.8556.7857.21
ABI.BR56.957.9856.7557.18
ABI.BR57.9658.9457.3958.77
ABI.BR58.6858.8657.8858.4
ABI.BR58.1658.457.4357.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

idsymboldate
021160ABI.BR2021-01-04
121161ABI.BR2021-01-05
221162ABI.BR2021-01-06
321163ABI.BR2021-01-07
421164ABI.BR2021-01-08
display(Markdown("**Columns at positions 0, 2, 4:**"))
display(ohlcv_pd.iloc[:, [0, 2, 4]].head())

Columns at positions 0, 2, 4

iddatehigh
0211602021-01-0458.85
1211612021-01-0557.98
2211622021-01-0658.94
3211632021-01-0758.86
4211642021-01-0858.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

idsymboldate
i64strdate
21160ABI.BR2021-01-04
21161ABI.BR2021-01-05
21162ABI.BR2021-01-06
21163ABI.BR2021-01-07
21164ABI.BR2021-01-08

Columns at positions 0, 2, 4

iddatehigh
i64datef64
211602021-01-0458.85
211612021-01-0557.98
211622021-01-0658.94
211632021-01-0758.86
211642021-01-0858.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))
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
display(Markdown("**All columns except volume:**"))
display(ohlcv_pl.select(pl.exclude("volume")).head())

All columns except volume

idsymboldateopenhighlowcloseadj_closedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.57610.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.5480.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.0370.00.0false
21163ABI.BR2021-01-0758.6858.8657.8858.454.69050.00.0false
21164ABI.BR2021-01-0858.1658.457.4357.8654.18480.00.0false
display(Markdown("**Exclude multiple columns:**"))
display(ohlcv_pl.select(pl.exclude(["volume", "symbol"])).head())

Exclude multiple columns

iddateopenhighlowcloseadj_closedividendsstock_splitsis_filled
i64datef64f64f64f64f64f64f64bool
211602021-01-0458.1558.8556.7857.2153.57610.00.0false
211612021-01-0556.957.9856.7557.1853.5480.00.0false
211622021-01-0657.9658.9457.3958.7755.0370.00.0false
211632021-01-0758.6858.8657.8858.454.69050.00.0false
211642021-01-0858.1658.457.4357.8654.18480.00.0false

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

idopenhighlowcloseadj_closevolumedividendsstock_splits
02116058.1558.8556.7857.2153.576115139370.00.0
12116156.9057.9856.7557.1853.548013827220.00.0
22116257.9658.9457.3958.7755.037013702040.00.0
32116358.6858.8657.8858.4054.690514699110.00.0
42116458.1658.4057.4357.8654.184814286810.00.0
display(Markdown("**Object / string columns only:**"))
display(ohlcv_pd.select_dtypes(include="object").head())

Object / string columns only

symboldate
0ABI.BR2021-01-04
1ABI.BR2021-01-05
2ABI.BR2021-01-06
3ABI.BR2021-01-07
4ABI.BR2021-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())
idopenhighlowcloseadj_closevolumedividendsstock_splits
i64f64f64f64f64f64i64f64f64
2116058.1558.8556.7857.2153.576115139370.00.0
2116156.957.9856.7557.1853.54813827220.00.0
2116257.9658.9457.3958.7755.03713702040.00.0
2116358.6858.8657.8858.454.690514699110.00.0
2116458.1658.457.4357.8654.184814286810.00.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

openhighlowcloseadj_closedividendsstock_splits
f64f64f64f64f64f64f64
58.1558.8556.7857.2153.57610.00.0
56.957.9856.7557.1853.5480.00.0
57.9658.9457.3958.7755.0370.00.0
58.6858.8657.8858.454.69050.00.0
58.1658.457.4357.8654.18480.00.0
display(Markdown("**Int64 columns only:**"))
display(ohlcv_pl.select(cs.by_dtype(pl.Int64)).head())

Int64 columns only

idvolume
i64i64
211601513937
211611382722
211621370204
211631469911
211641428681

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’)

symbolopenlowcloseadj_closevolumestock_splits
0ABI.BR58.1556.7857.2153.576115139370.0
1ABI.BR56.9056.7557.1853.548013827220.0
2ABI.BR57.9657.3958.7755.037013702040.0
3ABI.BR58.6857.8858.4054.690514699110.0
4ABI.BR58.1657.4357.8654.184814286810.0
display(Markdown("**Columns starting with 'c':**"))
display(ohlcv_pd.filter(regex="^c").head())

Columns starting with ‘c’

close
057.21
157.18
258.77
358.40
457.86
display(Markdown("**Columns whose name contains 'e':**"))
display(ohlcv_pd.filter(regex="e").head())

Columns whose name contains ‘e’

dateopencloseadj_closevolumedividendsis_filled
02021-01-0458.1557.2153.576115139370.0False
12021-01-0556.9057.1853.548013827220.0False
22021-01-0657.9658.7755.037013702040.0False
32021-01-0758.6858.4054.690514699110.0False
42021-01-0858.1657.8654.184814286810.0False

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())
openclose
f64f64
58.1557.21
56.957.18
57.9658.77
58.6858.4
58.1657.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)

openclose
f64f64
58.1557.21
56.957.18
57.9658.77
58.6858.4
58.1657.86
display(Markdown("**Columns ending with 'e':**"))
display(ohlcv_pl.select(pl.col("^.*e$")).head())

Columns ending with ‘e’

datecloseadj_closevolume
datef64f64i64
2021-01-0457.2153.57611513937
2021-01-0557.1853.5481382722
2021-01-0658.7755.0371370204
2021-01-0758.454.69051469911
2021-01-0857.8654.18481428681

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

openhighlowcloseadj_closedividendsstock_splits
f64f64f64f64f64f64f64
58.1558.8556.7857.2153.57610.00.0
56.957.9856.7557.1853.5480.00.0
57.9658.9457.3958.7755.0370.00.0
58.6858.8657.8858.454.69050.00.0
58.1658.457.4357.8654.18480.00.0
display(Markdown("**Numeric OR temporal:**"))
display(ohlcv_pl.select(cs.numeric() | cs.temporal()).head())

Numeric OR temporal

iddateopenhighlowcloseadj_closevolumedividendsstock_splits
i64datef64f64f64f64f64i64f64f64
211602021-01-0458.1558.8556.7857.2153.576115139370.00.0
211612021-01-0556.957.9856.7557.1853.54813827220.00.0
211622021-01-0657.9658.9457.3958.7755.03713702040.00.0
211632021-01-0758.6858.8657.8858.454.690514699110.00.0
211642021-01-0858.1658.457.4357.8654.184814286810.00.0
display(Markdown("**Invert a selector (everything NOT numeric):**"))
display(ohlcv_pl.select(~cs.numeric()).head())

Invert a selector (everything NOT numeric)

symboldateis_filled
strdatebool
ABI.BR2021-01-04false
ABI.BR2021-01-05false
ABI.BR2021-01-06false
ABI.BR2021-01-07false
ABI.BR2021-01-08false

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)
)
idsymboldateOpenhighlowCloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False

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)
)
idsymboldateOpenhighlowCloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false

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()
)
symboldateclosing_pricevol
strdatef64i64
ABI.BR2021-01-0457.211513937
ABI.BR2021-01-0557.181382722
ABI.BR2021-01-0658.771370204
ABI.BR2021-01-0758.41469911
ABI.BR2021-01-0857.861428681

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_idnum_opennum_highnum_lownum_closenum_adj_closenum_volumenum_dividendsnum_stock_splits
i64f64f64f64f64f64i64f64f64
2116058.1558.8556.7857.2153.576115139370.00.0
2116156.957.9856.7557.1853.54813827220.00.0
2116257.9658.9457.3958.7755.03713702040.00.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_rawsymbol_rawdate_rawopen_rawhigh_rawlow_rawclose_rawadj_close_rawvolume_rawdividends_rawstock_splits_rawis_filled_raw
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false

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))
datesymbolcloseopenhighlowvolume
02021-01-04ABI.BR57.2158.1558.8556.781513937
12021-01-05ABI.BR57.1856.9057.9856.751382722
22021-01-06ABI.BR58.7757.9658.9457.391370204

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)
)
datesymbolcloseopenhighlowvolume
datestrf64f64f64f64i64
2021-01-04ABI.BR57.2158.1558.8556.781513937
2021-01-05ABI.BR57.1856.957.9856.751382722
2021-01-06ABI.BR58.7757.9658.9457.391370204

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))
datesymbolidopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
datestri64f64f64f64f64f64i64f64f64bool
2021-01-04ABI.BR2116058.1558.8556.7857.2153.576115139370.00.0false
2021-01-05ABI.BR2116156.957.9856.7557.1853.54813827220.00.0false
2021-01-06ABI.BR2116257.9658.9457.3958.7755.03713702040.00.0false

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))
idsymboldateopenhighlowcloseadj_closedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.57610.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.54800.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.03700.00.0False
display(Markdown("**Drop multiple columns:**"))
display(ohlcv_pd.drop(columns=["volume", "open"]).head(3))

Drop multiple columns

idsymboldatehighlowcloseadj_closedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.8556.7857.2153.57610.00.0False
121161ABI.BR2021-01-0557.9856.7557.1853.54800.00.0False
221162ABI.BR2021-01-0658.9457.3958.7755.03700.00.0False

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))
idsymboldateopenhighlowcloseadj_closedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.57610.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.5480.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.0370.00.0false
display(Markdown("**Drop multiple columns:**"))
display(ohlcv_pl.drop("volume", "open").head(3))

Drop multiple columns

idsymboldatehighlowcloseadj_closedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64f64bool
21160ABI.BR2021-01-0458.8556.7857.2153.57610.00.0false
21161ABI.BR2021-01-0557.9856.7557.1853.5480.00.0false
21162ABI.BR2021-01-0658.9457.3958.7755.0370.00.0false

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

_indexsymbollong_nameshort_namesectorsector_keyindustryindustry_keycountrycitywebsiteexchangefull_exchange_nameexchange_timezone_nameexchange_timezone_shortcurrencyfinancial_currencyquote_typemarketrange_startprice_data_startvalid_to
0euro_stoxx_50ASML.ASASML Holding N.V.ASML HOLDINGTechnologytechnologySemiconductor Equipment & Materialssemiconductor-equipment-materialsNetherlandsVeldhovenhttps://www.asml.comAMSAmsterdamEurope/AmsterdamCETEUREUREQUITYnl_market1998-07-202021-01-01None
1euro_stoxx_50MC.PALVMH Moët Hennessy - Louis Vuitton, Société EuropéenneLVMHConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://www.lvmh.comPARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-01None
2euro_stoxx_50RMS.PAHermès International Société en commandite par actionsHERMES INTLConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://finance.hermes.comPARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-01None
3euro_stoxx_50OR.PAL'Oréal S.A.L'OREALConsumer Defensiveconsumer-defensiveHousehold & Personal Productshousehold-personal-productsFranceClichyhttps://www.loreal.comPARParisEurope/ParisCETEUREUREQUITYfr_market2000-01-032021-01-01None
4euro_stoxx_50SAP.DESAP SESAP SETechnologytechnologySoftware - Applicationsoftware-applicationGermanyWalldorfhttps://www.sap.comGERXETRAEurope/BerlinCETEUREUREQUITYde_market1998-04-092021-01-01None
display(Markdown("**Polars — select string columns with cs.string():**"))
display(dim_pl.select(cs.string()).head())

Polars | select string columns with cs.string()

_indexsymbollong_nameshort_namesectorsector_keyindustryindustry_keycountrycitywebsitelong_business_summaryexchangefull_exchange_nameexchange_timezone_nameexchange_timezone_shortcurrencyfinancial_currencyquote_typemarket
strstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstrstr
euro_stoxx_50ASML.ASASML Holding N.V.ASML HOLDINGTechnologytechnologySemiconductor Equipment & Mate…semiconductor-equipment-materi…NetherlandsVeldhovenhttps://www.asml.comASML Holding N.V. provides lit…AMSAmsterdamEurope/AmsterdamCETEUREUREQUITYnl_market
euro_stoxx_50MC.PALVMH Moët Hennessy - Louis Vui…LVMHConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://www.lvmh.comLVMH Moët Hennessy - Louis Vui…PARParisEurope/ParisCETEUREUREQUITYfr_market
euro_stoxx_50RMS.PAHermès International Société e…HERMES INTLConsumer Cyclicalconsumer-cyclicalLuxury Goodsluxury-goodsFranceParishttps://finance.hermes.comHermès International Société e…PARParisEurope/ParisCETEUREUREQUITYfr_market
euro_stoxx_50OR.PAL'Oréal S.A.L'OREALConsumer Defensiveconsumer-defensiveHousehold & Personal Productshousehold-personal-productsFranceClichyhttps://www.loreal.comL'Oréal S.A., through its subs…PARParisEurope/ParisCETEUREUREQUITYfr_market
euro_stoxx_50SAP.DESAP SESAP SETechnologytechnologySoftware - Applicationsoftware-applicationGermanyWalldorfhttps://www.sap.comSAP SE, together with its subs…GERXETRAEurope/BerlinCETEUREUREQUITYde_market
display(Markdown("**Polars — select with cs.matches() regex:**"))
display(dim_pl.select(cs.matches(".*name.*|.*id.*")).head())

Polars | select with cs.matches() regex

idlong_nameshort_namefull_exchange_nameexchange_timezone_namevalid_fromvalid_to
i64strstrstrstrdatetime[ns]null
1ASML Holding N.V.ASML HOLDINGAmsterdamEurope/Amsterdam2026-03-04 22:11:36.189862null
2LVMH Moët Hennessy - Louis Vui…LVMHParisEurope/Paris2026-03-04 22:11:36.189862null
3Hermès International Société e…HERMES INTLParisEurope/Paris2026-03-04 22:11:36.193940null
4L'Oréal S.A.L'OREALParisEurope/Paris2026-03-04 22:11:36.193940null
5SAP SESAP SEXETRAEurope/Berlin2026-03-04 22:11:36.193940null

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))
TaskPandasPolars
Single column (Series)df["col"]df.get_column("col")
Single column (DataFrame)df[["col"]]df.select("col")
Multiple columnsdf[["a","b"]]df.select("a","b")
Label-based slicedf.loc[:, "a":"c"](use explicit list)
Position-baseddf.iloc[:, 0:3]df[:, 0:3]
All columnsdfdf.select(pl.all())
Exclude columnsdf.drop(columns=[...])df.select(pl.exclude(...))
Numeric columnsdf.select_dtypes("number")df.select(cs.numeric())
String columnsdf.select_dtypes("object")df.select(cs.string())
Temporal columnsdf.select_dtypes("datetime")df.select(cs.temporal())
By specific dtypedf.select_dtypes(include=...)df.select(cs.by_dtype(...))
By name patterndf.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)
Renamedf.rename(columns={...})df.rename({...})
Alias in expr(not applicable)pl.col("x").alias("y")
Prefix / suffixdf.add_prefix("p_")cs.numeric().name.prefix("p_")
Reorderdf[new_order]df.select(new_order)
Drop columnsdf.drop(columns=[...])df.drop("a","b")
Regex selectdf.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. Pandas df[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)
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
ohlcv_pl.head(3)
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false

Boolean Indexing (Single Condition)

Chained indexing in Pandas

Chained indexing in Pandas — df[condition]["col"] = value silently fails df[df["close"] > 50]["close"] = 0 looks like it works but modifies a copy, not the original DataFrame. Pandas raises SettingWithCopyWarning but 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, use pl.when(condition).then(value).otherwise(pl.col("col")) inside with_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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
321163ABI.BR2021-01-0758.6858.8657.8858.4054.690514699110.00.0False
421164ABI.BR2021-01-0858.1658.4057.4357.8654.184814286810.00.0False

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
321163ABI.BR2021-01-0758.6858.8657.8858.4054.690514699110.00.0False
421164ABI.BR2021-01-0858.1658.4057.4357.8654.184814286810.00.0False

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
21163ABI.BR2021-01-0758.6858.8657.8858.454.690514699110.00.0false
21164ABI.BR2021-01-0858.1658.457.4357.8654.184814286810.00.0false

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
321163ABI.BR2021-01-0758.6858.8657.8858.4054.690514699110.00.0False
421164ABI.BR2021-01-0858.1658.4057.4357.8654.184814286810.00.0False
# OR: close < 10 OR close > 100
mask = (ohlcv_pd["close"] < 10) | (ohlcv_pd["close"] > 100)
ohlcv_pd.loc[mask].head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
266262088ADS.DE2021-01-04300.0300.5293.0295.4282.29044403640.00.0False
266362089ADS.DE2021-01-05292.9295.4288.2289.6276.74794365910.00.0False
266462090ADS.DE2021-01-06290.7292.7286.8291.7278.75463926020.00.0False
266562091ADS.DE2021-01-07294.0294.1288.5288.5275.69673628090.00.0False
266662092ADS.DE2021-01-08292.3296.8292.0295.1282.00384257620.00.0False
# NOT: rows where volume is NOT above 5_000_000
mask = ~(ohlcv_pd["volume"] > 5_000_000)
ohlcv_pd.loc[mask].head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
321163ABI.BR2021-01-0758.6858.8657.8858.4054.690514699110.00.0False
421164ABI.BR2021-01-0858.1658.4057.4357.8654.184814286810.00.0False

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
21163ABI.BR2021-01-0758.6858.8657.8858.454.690514699110.00.0false
21164ABI.BR2021-01-0858.1658.457.4357.8654.184814286810.00.0false
ohlcv_pl.filter(
    (pl.col("close") < 10) | (pl.col("close") > 100)
).head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
62088ADS.DE2021-01-04300.0300.5293.0295.4282.29044403640.00.0false
62089ADS.DE2021-01-05292.9295.4288.2289.6276.74794365910.00.0false
62090ADS.DE2021-01-06290.7292.7286.8291.7278.75463926020.00.0false
62091ADS.DE2021-01-07294.0294.1288.5288.5275.69673628090.00.0false
62092ADS.DE2021-01-08292.3296.8292.0295.1282.00384257620.00.0false
ohlcv_pl.filter(
    ~(pl.col("volume") > 5_000_000)
).head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
21163ABI.BR2021-01-0758.6858.8657.8858.454.690514699110.00.0false
21164ABI.BR2021-01-0858.1658.457.4357.8654.184814286810.00.0false

query() (Pandas Only)

ohlcv_pd.query("close > 50 and volume > 1_000_000").head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
321163ABI.BR2021-01-0758.6858.8657.8858.4054.690514699110.00.0False
421164ABI.BR2021-01-0858.1658.4057.4357.8654.184814286810.00.0False
# Using variables with @
threshold = 80
ohlcv_pd.query("close > @threshold").head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
266262088ADS.DE2021-01-04300.0300.5293.0295.4282.29044403640.00.0False
266362089ADS.DE2021-01-05292.9295.4288.2289.6276.74794365910.00.0False
266462090ADS.DE2021-01-06290.7292.7286.8291.7278.75463926020.00.0False
266562091ADS.DE2021-01-07294.0294.1288.5288.5275.69673628090.00.0False
266662092ADS.DE2021-01-08292.3296.8292.0295.1282.00384257620.00.0False
# String column comparisons in query
tickers = ["SIE.DE", "SAP.DE"]
ohlcv_pd.query("symbol in @tickers").head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
557385301SAP.DE2021-01-04108.10108.50104.78105.3297.010229285150.00.0False
557395302SAP.DE2021-01-05104.98106.20104.46105.0496.752327988880.00.0False
557405303SAP.DE2021-01-06105.14106.26103.60105.4897.157630188020.00.0False
557415304SAP.DE2021-01-07105.58105.70104.04104.5296.273431761430.00.0False
557425305SAP.DE2021-01-08105.14106.72105.04106.1897.802430687440.00.0False

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
1196552834BAS.DE2021-01-0465.4866.0764.4364.8947.486227415080.00.0False
1196652835BAS.DE2021-01-0564.3065.4763.2664.4047.127727703370.00.0False
1196752836BAS.DE2021-01-0665.2467.5565.1467.3749.301151872510.00.0False
1196852837BAS.DE2021-01-0767.9368.5367.1368.4150.062236553660.00.0False
1196952838BAS.DE2021-01-0869.0069.2468.0168.5850.186630357330.00.0False

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
52834BAS.DE2021-01-0465.4866.0764.4364.8947.486227415080.00.0false
52835BAS.DE2021-01-0564.365.4763.2664.447.127727703370.00.0false
52836BAS.DE2021-01-0665.2467.5565.1467.3749.301151872510.00.0false
52837BAS.DE2021-01-0767.9368.5367.1368.4150.062236553660.00.0false
52838BAS.DE2021-01-0869.069.2468.0168.5850.186630357330.00.0false

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
321163ABI.BR2021-01-0758.6858.8657.8858.4054.690514699110.00.0False
421164ABI.BR2021-01-0858.1658.4057.4357.8654.184814286810.00.0False

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
21163ABI.BR2021-01-0758.6858.8657.8858.454.690514699110.00.0false
21164ABI.BR2021-01-0858.1658.457.4357.8654.184814286810.00.0false
# 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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21675ABI.BR2023-01-0256.6357.0956.4356.954.24536084370.00.0false
21676ABI.BR2023-01-0356.7957.7656.7256.8454.188111648090.00.0false
21677ABI.BR2023-01-0456.9658.0256.9458.0255.31318355120.00.0false
21678ABI.BR2023-01-0557.6957.9857.057.1454.474113242500.00.0false
21679ABI.BR2023-01-0657.2357.4757.0357.4454.760111000100.00.0false

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_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
0163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.261140NaN2.3889621.52116310.0161231.0090901.1302640.0824860.477966160.1531571.84211False0.052711250.68394712026-03-04 22:40:25.48918092.08500081.181889997512151040.019525BNP PARIBAS ACT.AFrance89.3200.011437-0.0731560.105582EUR
8188euro_stoxx_50SAN.MC2026-03-04Financial Services0.4585990.499398NaN-1.107590-0.049864330.3931970.9538241.1395190.1134990.519307140.2247041.70000False0.448776150.30607392026-03-04 22:40:25.48918010.6049009.9195001459557498880.028570BANCO SANTANDER S.A.Spain9.9820.038818-0.105876-0.008739EUR
15176euro_stoxx_50ISP.MI2026-03-04Financial Services0.3666190.488302NaN0.7233610.52609413-0.0652470.9224920.9908550.119662-0.122626330.2541502.00000False0.146959210.183476162026-03-04 22:40:25.4891805.8349335.769367942683176960.018452INTESA SANPAOLOItaly5.4220.018216-0.067103-0.084276EUR
16195euro_stoxx_50UCG.MI2026-03-04Financial Services0.4525010.355225NaN-0.2606740.182351260.0858670.9508891.0544300.1378620.123670220.2615211.94444False0.240819180.182280172026-03-04 22:40:25.48918073.25333368.9835561030665011200.020174UNICREDITItaly68.7900.027483-0.072161-0.030034EUR
20175euro_stoxx_50INGA.AS2026-03-04Financial Services0.3799170.727879NaN-0.2405090.289096210.1130860.9462271.0708150.1187370.192271210.2014592.10526False-0.145505300.111954212026-03-04 22:40:25.48918024.76600023.632333674543820800.013204ING GROEP N.V.Netherlands23.3050.018798-0.066867-0.029363EUR
# Rows where a column is NOT null
scores_pd[scores_pd["ev_ebitda_zscore"].notna()].head()
id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
1168euro_stoxx_50DTE.DE2026-03-04Communication Services0.3265870.3874630.379532-0.1278670.24142924-0.2055981.1206501.1124160.0555240.68575280.1212121.33333False0.617835100.51500522026-03-04 22:40:25.48918030.83800028.5545561642943119360.032159DEUTSCHE TELEKOM AGGermany33.0000.011649-0.0196080.193059EUR
2174euro_stoxx_50IFX.DE2026-03-04Technology0.5093980.6372150.677068-0.6966620.281755220.0009651.0482441.1986260.0888450.67576490.1264081.37500False0.579187110.51223532026-03-04 22:40:25.48918043.48033338.855556572225331200.011201INFINEON TECHNOLOGIES AGGermany43.9450.054343-0.0664900.164723EUR
3172euro_stoxx_50ENR.DE2026-03-04Industrials-0.902738-0.743338-1.693212-1.326075-1.166341461.6454551.1370071.4740950.0518502.54188910.0752691.80000False-0.123264290.41742842026-03-04 22:40:25.489180155.675000129.1220001392072622080.027249Siemens Energy AGGermany162.7500.047297-0.0392560.351744EUR
4149euro_stoxx_50ABI.BR2026-03-04Consumer Defensive0.4740840.8440750.552739-0.9750050.22397325-0.0297911.0585421.1427830.0630630.651891100.1861981.69231False0.344755170.40687352026-03-04 22:40:25.48918064.34200057.8693331255661568000.024579AB INBEVBelgium64.480-0.017073-0.0407620.174499EUR
5196euro_stoxx_50VOW.DE2026-03-04Consumer Cyclical1.1662210.9402730.3798301.5288911.0038042-0.2927480.9293920.9696280.180805-0.411969370.297071NaNFalse0.555357120.38239762026-03-04 22:40:25.489180102.286667101.225556479238266880.009381VOLKSWAGEN AGGermany95.6000.013786-0.048756-0.090390EUR

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_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
i64strstrdatestrf64f64f64f64f64i64f64f64f64f64f64i64f64f64boolf64i64f64i64datetime[ns]f64f64i64f64strstrf64f64f64f64str
163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.26114null2.3889621.52116310.0161231.009091.1302640.0824860.477966160.1531571.84211false0.052711250.68394712026-03-04 22:40:25.48918092.08581.181889997512151040.019525BNP PARIBAS ACT.AFrance89.320.011437-0.0731560.105582EUR
188euro_stoxx_50SAN.MC2026-03-04Financial Services0.4585990.499398null-1.10759-0.049864330.3931970.9538241.1395190.1134990.519307140.2247041.7false0.448776150.30607392026-03-04 22:40:25.48918010.60499.91951459557498880.02857BANCO SANTANDER S.A.Spain9.9820.038818-0.105876-0.008739EUR
176euro_stoxx_50ISP.MI2026-03-04Financial Services0.3666190.488302null0.7233610.52609413-0.0652470.9224920.9908550.119662-0.122626330.254152.0false0.146959210.183476162026-03-04 22:40:25.4891805.8349335.769367942683176960.018452INTESA SANPAOLOItaly5.4220.018216-0.067103-0.084276EUR
195euro_stoxx_50UCG.MI2026-03-04Financial Services0.4525010.355225null-0.2606740.182351260.0858670.9508891.054430.1378620.12367220.2615211.94444false0.240819180.18228172026-03-04 22:40:25.48918073.25333368.9835561030665011200.020174UNICREDITItaly68.790.027483-0.072161-0.030034EUR
175euro_stoxx_50INGA.AS2026-03-04Financial Services0.3799170.727879null-0.2405090.289096210.1130860.9462271.0708150.1187370.192271210.2014592.10526false-0.145505300.111954212026-03-04 22:40:25.48918024.76623.632333674543820800.013204ING GROEP N.V.Netherlands23.3050.018798-0.066867-0.029363EUR
scores_pl.filter(pl.col("ev_ebitda_zscore").is_not_null()).head()
id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
i64strstrdatestrf64f64f64f64f64i64f64f64f64f64f64i64f64f64boolf64i64f64i64datetime[ns]f64f64i64f64strstrf64f64f64f64str
168euro_stoxx_50DTE.DE2026-03-04Communication Services0.3265870.3874630.379532-0.1278670.24142924-0.2055981.120651.1124160.0555240.68575280.1212121.33333false0.617835100.51500522026-03-04 22:40:25.48918030.83828.5545561642943119360.032159DEUTSCHE TELEKOM AGGermany33.00.011649-0.0196080.193059EUR
174euro_stoxx_50IFX.DE2026-03-04Technology0.5093980.6372150.677068-0.6966620.281755220.0009651.0482441.1986260.0888450.67576490.1264081.375false0.579187110.51223532026-03-04 22:40:25.48918043.48033338.855556572225331200.011201INFINEON TECHNOLOGIES AGGermany43.9450.054343-0.066490.164723EUR
172euro_stoxx_50ENR.DE2026-03-04Industrials-0.902738-0.743338-1.693212-1.326075-1.166341461.6454551.1370071.4740950.051852.54188910.0752691.8false-0.123264290.41742842026-03-04 22:40:25.489180155.675129.1221392072622080.027249Siemens Energy AGGermany162.750.047297-0.0392560.351744EUR
149euro_stoxx_50ABI.BR2026-03-04Consumer Defensive0.4740840.8440750.552739-0.9750050.22397325-0.0297911.0585421.1427830.0630630.651891100.1861981.69231false0.344755170.40687352026-03-04 22:40:25.48918064.34257.8693331255661568000.024579AB INBEVBelgium64.48-0.017073-0.0407620.174499EUR
196euro_stoxx_50VOW.DE2026-03-04Consumer Cyclical1.1662210.9402730.379831.5288911.0038042-0.2927480.9293920.9696280.180805-0.411969370.297071nullfalse0.555357120.38239762026-03-04 22:40:25.489180102.286667101.225556479238266880.009381VOLKSWAGEN AGGermany95.60.013786-0.048756-0.09039EUR

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
057.21
157.18
258.77
358.40
457.86
556.61
656.51
756.48
856.96
956.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
057.21
157.18
258.77
358.40
457.86
556.61
656.51
756.48
856.96
956.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)
symboldateclosevolumeclose_filtered
strdatef64i64f64
ABI.BR2021-01-0457.21151393757.21
ABI.BR2021-01-0557.18138272257.18
ABI.BR2021-01-0658.77137020458.77
ABI.BR2021-01-0758.4146991158.4
ABI.BR2021-01-0857.86142868157.86
ABI.BR2021-01-1156.61151807956.61
ABI.BR2021-01-1256.51164999156.51
ABI.BR2021-01-1356.48109080656.48
ABI.BR2021-01-1456.96152304556.96
ABI.BR2021-01-1556.74176998856.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)
symboldatecloseprice_bucket
strdatef64str
ABI.BR2021-01-0457.21mid
ABI.BR2021-01-0557.18mid
ABI.BR2021-01-0658.77mid
ABI.BR2021-01-0758.4mid
ABI.BR2021-01-0857.86mid
ABI.BR2021-01-1156.61mid
ABI.BR2021-01-1256.51mid
ABI.BR2021-01-1356.48mid
ABI.BR2021-01-1456.96mid
ABI.BR2021-01-1556.74mid

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
5174718512SAF.PA2021-01-04117.35121.00116.1116.15111.64476587640.00.0False
5174818513SAF.PA2021-01-05114.90116.95114.8116.40111.88505887650.00.0False
5174918514SAF.PA2021-01-06117.55117.55115.4116.30111.78885815430.00.0False
5175018515SAF.PA2021-01-07117.05117.30114.8115.80111.30826356050.00.0False
5175118516SAF.PA2021-01-08116.90117.00115.0116.35111.83696884600.00.0False
# Tickers containing "DE"
ohlcv_pd[ohlcv_pd["symbol"].str.contains("DE")].head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
266262088ADS.DE2021-01-04300.0300.5293.0295.4282.29044403640.00.0False
266362089ADS.DE2021-01-05292.9295.4288.2289.6276.74794365910.00.0False
266462090ADS.DE2021-01-06290.7292.7286.8291.7278.75463926020.00.0False
266562091ADS.DE2021-01-07294.0294.1288.5288.5275.69673628090.00.0False
266662092ADS.DE2021-01-08292.3296.8292.0295.1282.00384257620.00.0False

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
18512SAF.PA2021-01-04117.35121.0116.1116.15111.64476587640.00.0false
18513SAF.PA2021-01-05114.9116.95114.8116.4111.8855887650.00.0false
18514SAF.PA2021-01-06117.55117.55115.4116.3111.78885815430.00.0false
18515SAF.PA2021-01-07117.05117.3114.8115.8111.30826356050.00.0false
18516SAF.PA2021-01-08116.9117.0115.0116.35111.83696884600.00.0false
ohlcv_pl.filter(pl.col("symbol").str.contains("DE")).head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
62088ADS.DE2021-01-04300.0300.5293.0295.4282.29044403640.00.0false
62089ADS.DE2021-01-05292.9295.4288.2289.6276.74794365910.00.0false
62090ADS.DE2021-01-06290.7292.7286.8291.7278.75463926020.00.0false
62091ADS.DE2021-01-07294.0294.1288.5288.5275.69673628090.00.0false
62092ADS.DE2021-01-08292.3296.8292.0295.1282.00384257620.00.0false

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
321163ABI.BR2021-01-0758.6858.8657.8858.4054.690514699110.00.0False
421164ABI.BR2021-01-0858.1658.4057.4357.8654.184814286810.00.0False
# Filter by year
ohlcv_pd[ohlcv_pd["date"].dt.year == 2023].head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
51521675ABI.BR2023-01-0256.6357.0956.4356.9054.24536084370.00.0False
51621676ABI.BR2023-01-0356.7957.7656.7256.8454.188111648090.00.0False
51721677ABI.BR2023-01-0456.9658.0256.9458.0255.313018355120.00.0False
51821678ABI.BR2023-01-0557.6957.9857.0057.1454.474113242500.00.0False
51921679ABI.BR2023-01-0657.2357.4757.0357.4454.760111000100.00.0False
# Filter by day of week (Monday=0)
ohlcv_pd[ohlcv_pd["date"].dt.dayofweek == 0].head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
521165ABI.BR2021-01-1157.7357.8156.3956.6153.014215180790.00.0False
1021170ABI.BR2021-01-1856.2557.3056.2057.0853.45447302980.00.0False
1521175ABI.BR2021-01-2554.7754.8052.8953.1749.792719745470.00.0False
2021180ABI.BR2021-02-0152.3253.3052.1552.5849.240215436050.00.0False

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
21163ABI.BR2021-01-0758.6858.8657.8858.454.690514699110.00.0false
21164ABI.BR2021-01-0858.1658.457.4357.8654.184814286810.00.0false
ohlcv_pl.filter(pl.col("date").dt.year() == 2023).head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21675ABI.BR2023-01-0256.6357.0956.4356.954.24536084370.00.0false
21676ABI.BR2023-01-0356.7957.7656.7256.8454.188111648090.00.0false
21677ABI.BR2023-01-0456.9658.0256.9458.0255.31318355120.00.0false
21678ABI.BR2023-01-0557.6957.9857.057.1454.474113242500.00.0false
21679ABI.BR2023-01-0657.2357.4757.0357.4454.760111000100.00.0false
ohlcv_pl.filter(pl.col("date").dt.weekday() == 1).head()  # Monday=1 in Polars
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21165ABI.BR2021-01-1157.7357.8156.3956.6153.014215180790.00.0false
21170ABI.BR2021-01-1856.2557.356.257.0853.45447302980.00.0false
21175ABI.BR2021-01-2554.7754.852.8953.1749.792719745470.00.0false
21180ABI.BR2021-02-0152.3253.352.1552.5849.240215436050.00.0false

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]
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
1021170ABI.BR2021-01-1856.2557.3056.2057.0853.45447302980.00.0False
1121171ABI.BR2021-01-1957.1057.2656.2656.3552.770711165700.00.0False
1221172ABI.BR2021-01-2056.3556.7756.0056.2452.667712265160.00.0False
1321173ABI.BR2021-01-2156.2056.5555.3155.3151.796814042830.00.0False
1421174ABI.BR2021-01-2255.2855.2854.1254.7851.300515572870.00.0False
# Pandas — sample
ohlcv_pd.sample(5, random_state=42)
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
4305338920MUV2.DE2023-03-29320.0000322.600318.400322.4000290.32001958090.00.0False
562095772SAP.DE2022-11-0395.920096.34095.08095.510091.905214249730.00.0False
5351511015SAN.MC2022-09-152.59752.6862.5972.67652.3373701583490.00.0False
649828954AI.PA2025-08-12173.3200174.440172.800173.6800173.68004156520.00.0False
6352724956UCG.MI2025-07-0756.460057.35056.44057.350056.046847055670.00.0False
# Polars
ohlcv_pl.head(3)
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
ohlcv_pl.tail(3)
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
66876WKL.AS2026-03-1068.869.1666.3467.1667.1613556450.00.0false
66877WKL.AS2026-03-1167.569.667.0267.2267.2211425310.00.0false
66929WKL.AS2026-03-1267.067.5466.2867.3267.322103790.00.0false
ohlcv_pl.slice(10, 5)  # offset, length
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21170ABI.BR2021-01-1856.2557.356.257.0853.45447302980.00.0false
21171ABI.BR2021-01-1957.157.2656.2656.3552.770711165700.00.0false
21172ABI.BR2021-01-2056.3556.7756.056.2452.667712265160.00.0false
21173ABI.BR2021-01-2156.256.5555.3155.3151.796814042830.00.0false
21174ABI.BR2021-01-2255.2855.2854.1254.7851.300515572870.00.0false
ohlcv_pl.sample(5, seed=42)
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
11529SAN.MC2024-09-184.5114.54554.50654.50854.2785164872380.00.0false
35604CS.PA2025-10-1439.3440.2739.2540.1840.1835111250.00.0false
63666WKL.AS2022-01-05102.2102.65101.25101.895.2542305090.00.0false
33136PRX.AS2021-05-0341.365441.73741.071841.374640.858121775250.00.0false
19417SAF.PA2024-07-12204.2204.8201.3204.8202.51574967390.00.0false

unique / drop_duplicates / drop_nulls / dropna

# Pandas — unique tickers
ohlcv_pd["symbol"].drop_duplicates().head(10)
symbol
0ABI.BR
1331AD.AS
2662ADS.DE
3986ADYEN.AS
5317AI.PA
6648AIR.PA
7979ALV.DE
9303ARGX.BR
10634ASML.AS
11965BAS.DE
# Pandas — drop_duplicates on subset
ohlcv_pd.drop_duplicates(subset=["symbol"]).head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.150058.850056.780057.210053.576115139370.00.0False
133159438AD.AS2021-01-0423.380023.830023.380023.790019.933935261650.00.0False
266262088ADS.DE2021-01-04300.0000300.5000293.0000295.4000282.29044403640.00.0False
398660763ADYEN.AS2021-01-041900.00001921.50001856.00001859.50001859.5000994080.00.0False
531727773AI.PA2021-01-04112.3554113.6364111.9835112.7686102.96259179820.00.0False
# Pandas — dropna
scores_pd.dropna(subset=["ev_ebitda_zscore"]).head()
id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
1168euro_stoxx_50DTE.DE2026-03-04Communication Services0.3265870.3874630.379532-0.1278670.24142924-0.2055981.1206501.1124160.0555240.68575280.1212121.33333False0.617835100.51500522026-03-04 22:40:25.48918030.83800028.5545561642943119360.032159DEUTSCHE TELEKOM AGGermany33.0000.011649-0.0196080.193059EUR
2174euro_stoxx_50IFX.DE2026-03-04Technology0.5093980.6372150.677068-0.6966620.281755220.0009651.0482441.1986260.0888450.67576490.1264081.37500False0.579187110.51223532026-03-04 22:40:25.48918043.48033338.855556572225331200.011201INFINEON TECHNOLOGIES AGGermany43.9450.054343-0.0664900.164723EUR
3172euro_stoxx_50ENR.DE2026-03-04Industrials-0.902738-0.743338-1.693212-1.326075-1.166341461.6454551.1370071.4740950.0518502.54188910.0752691.80000False-0.123264290.41742842026-03-04 22:40:25.489180155.675000129.1220001392072622080.027249Siemens Energy AGGermany162.7500.047297-0.0392560.351744EUR
4149euro_stoxx_50ABI.BR2026-03-04Consumer Defensive0.4740840.8440750.552739-0.9750050.22397325-0.0297911.0585421.1427830.0630630.651891100.1861981.69231False0.344755170.40687352026-03-04 22:40:25.48918064.34200057.8693331255661568000.024579AB INBEVBelgium64.480-0.017073-0.0407620.174499EUR
5196euro_stoxx_50VOW.DE2026-03-04Consumer Cyclical1.1662210.9402730.3798301.5288911.0038042-0.2927480.9293920.9696280.180805-0.411969370.297071NaNFalse0.555357120.38239762026-03-04 22:40:25.489180102.286667101.225556479238266880.009381VOLKSWAGEN AGGermany95.6000.013786-0.048756-0.090390EUR
# 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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
17194ENR.DE2021-01-0430.6231.229.9130.1329.848116932650.00.0false
1ASML.AS2021-01-04404.0411.0402.25406.25387.7097895020.00.0false
63406WKL.AS2021-01-0469.7871.369.7670.8665.17855161760.00.0false
42307ENI.MI2021-01-048.6048.7568.3978.4486.0466197340040.00.0false
19837IBE.MC2021-01-0411.811.94511.7911.9059.4733142136720.00.0false
# Polars — drop_nulls
scores_pl.drop_nulls(subset=["ev_ebitda_zscore"]).head()
id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
i64strstrdatestrf64f64f64f64f64i64f64f64f64f64f64i64f64f64boolf64i64f64i64datetime[ns]f64f64i64f64strstrf64f64f64f64str
168euro_stoxx_50DTE.DE2026-03-04Communication Services0.3265870.3874630.379532-0.1278670.24142924-0.2055981.120651.1124160.0555240.68575280.1212121.33333false0.617835100.51500522026-03-04 22:40:25.48918030.83828.5545561642943119360.032159DEUTSCHE TELEKOM AGGermany33.00.011649-0.0196080.193059EUR
174euro_stoxx_50IFX.DE2026-03-04Technology0.5093980.6372150.677068-0.6966620.281755220.0009651.0482441.1986260.0888450.67576490.1264081.375false0.579187110.51223532026-03-04 22:40:25.48918043.48033338.855556572225331200.011201INFINEON TECHNOLOGIES AGGermany43.9450.054343-0.066490.164723EUR
172euro_stoxx_50ENR.DE2026-03-04Industrials-0.902738-0.743338-1.693212-1.326075-1.166341461.6454551.1370071.4740950.051852.54188910.0752691.8false-0.123264290.41742842026-03-04 22:40:25.489180155.675129.1221392072622080.027249Siemens Energy AGGermany162.750.047297-0.0392560.351744EUR
149euro_stoxx_50ABI.BR2026-03-04Consumer Defensive0.4740840.8440750.552739-0.9750050.22397325-0.0297911.0585421.1427830.0630630.651891100.1861981.69231false0.344755170.40687352026-03-04 22:40:25.48918064.34257.8693331255661568000.024579AB INBEVBelgium64.48-0.017073-0.0407620.174499EUR
196euro_stoxx_50VOW.DE2026-03-04Consumer Cyclical1.1662210.9402730.379831.5288911.0038042-0.2927480.9293920.9696280.180805-0.411969370.297071nullfalse0.555357120.38239762026-03-04 22:40:25.489180102.286667101.225556479238266880.009381VOLKSWAGEN AGGermany95.60.013786-0.048756-0.09039EUR

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
514733708RMS.PA2025-02-142926.02957.02813.02839.02802.93821056510.00.0False
514723707RMS.PA2025-02-132770.02816.02765.02816.02780.2302800870.00.0False
514743709RMS.PA2025-02-172825.02858.02803.02809.02776.7424538523.50.0False
514753710RMS.PA2025-02-182816.02827.02780.02806.02773.7771654690.00.0False
415060927ADYEN.AS2021-08-242725.02766.02711.52766.02766.0000614310.00.0False
# Multi-column sort
ohlcv_pd.sort_values(["symbol", "date"], ascending=[True, False]).head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
133066897ABI.BR2026-03-1262.6462.9662.0862.7662.763036480.00.0False
132966781ABI.BR2026-03-1162.6463.3862.3862.6862.6816798070.00.0False
132866780ABI.BR2026-03-1062.7463.3462.2463.3263.3218287030.00.0False
132766779ABI.BR2026-03-0961.7262.7061.5062.5862.5818802540.00.0False
132664764ABI.BR2026-03-0663.4663.6462.3263.1463.1425198690.00.0False

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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
3708RMS.PA2025-02-142926.02957.02813.02839.02802.93821056510.00.0false
3707RMS.PA2025-02-132770.02816.02765.02816.02780.2302800870.00.0false
3709RMS.PA2025-02-172825.02858.02803.02809.02776.7424538523.50.0false
3710RMS.PA2025-02-182816.02827.02780.02806.02773.7771654690.00.0false
60927ADYEN.AS2021-08-242725.02766.02711.52766.02766.0614310.00.0false
# Multi-column sort
ohlcv_pl.sort(["symbol", "date"], descending=[False, True]).head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
66897ABI.BR2026-03-1262.6462.9662.0862.7662.763036480.00.0false
66781ABI.BR2026-03-1162.6463.3862.3862.6862.6816798070.00.0false
66780ABI.BR2026-03-1062.7463.3462.2463.3263.3218287030.00.0false
66779ABI.BR2026-03-0961.7262.761.562.5862.5818802540.00.0false
64764ABI.BR2026-03-0663.4663.6462.3263.1463.1425198690.00.0false
# 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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filledclose_chronological
i64strdatef64f64f64f64f64i64f64f64boolf64
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false57.21
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false57.18
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false58.77
21163ABI.BR2021-01-0758.6858.8657.8858.454.690514699110.00.0false58.4
21164ABI.BR2021-01-0858.1658.457.4357.8654.184814286810.00.0false57.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()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
021160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0False
121161ABI.BR2021-01-0556.9057.9856.7557.1853.548013827220.00.0False
221162ABI.BR2021-01-0657.9658.9457.3958.7755.037013702040.00.0False
321163ABI.BR2021-01-0758.6858.8657.8858.4054.690514699110.00.0False
421164ABI.BR2021-01-0858.1658.4057.4357.8654.184814286810.00.0False
# Polars — semi join
ohlcv_pl.join(dim_pl.select("symbol").unique(), on="symbol", how="semi").head()
idsymboldateopenhighlowcloseadj_closevolumedividendsstock_splitsis_filled
i64strdatef64f64f64f64f64i64f64f64bool
21160ABI.BR2021-01-0458.1558.8556.7857.2153.576115139370.00.0false
21161ABI.BR2021-01-0556.957.9856.7557.1853.54813827220.00.0false
21162ABI.BR2021-01-0657.9658.9457.3958.7755.03713702040.00.0false
21163ABI.BR2021-01-0758.6858.8657.8858.454.690514699110.00.0false
21164ABI.BR2021-01-0858.1658.457.4357.8654.184814286810.00.0false

Filtering scores_daily — Practical Examples

scores_pd.head(3)
id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
0163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.261140NaN2.3889621.52116310.0161231.0090901.1302640.0824860.477966160.1531571.84211False0.052711250.68394712026-03-04 22:40:25.48918092.08500081.181889997512151040.019525BNP PARIBAS ACT.AFrance89.3200.011437-0.0731560.105582EUR
1168euro_stoxx_50DTE.DE2026-03-04Communication Services0.3265870.3874630.379532-0.1278670.24142924-0.2055981.1206501.1124160.0555240.68575280.1212121.33333False0.617835100.51500522026-03-04 22:40:25.48918030.83800028.5545561642943119360.032159DEUTSCHE TELEKOM AGGermany33.0000.011649-0.0196080.193059EUR
2174euro_stoxx_50IFX.DE2026-03-04Technology0.5093980.6372150.677068-0.6966620.281755220.0009651.0482441.1986260.0888450.67576490.1264081.37500False0.579187110.51223532026-03-04 22:40:25.48918043.48033338.855556572225331200.011201INFINEON TECHNOLOGIES AGGermany43.9450.054343-0.0664900.164723EUR
# Pandas — top scores
scores_pd[scores_pd["relative_value_score"] > 0.8].head()
id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
0163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.261140NaN2.3889621.52116310.0161231.0090901.1302640.0824860.477966160.1531571.84211False0.052711250.68394712026-03-04 22:40:25.48918092.08500081.181889997512151040.019525BNP PARIBAS ACT.AFrance89.320.011437-0.0731560.105582EUR
5196euro_stoxx_50VOW.DE2026-03-04Consumer Cyclical1.1662210.9402730.3798301.5288911.0038042-0.2927480.9293920.9696280.180805-0.411969370.297071NaNFalse0.555357120.38239762026-03-04 22:40:25.489180102.286667101.225556479238266880.009381VOLKSWAGEN AGGermany95.600.013786-0.048756-0.090390EUR
7166euro_stoxx_50DG.PA2026-03-04Industrials0.7785730.8509850.9287971.1462680.9261563-0.0316971.0643531.0958090.0628710.595820110.0436082.04762False-0.538059340.32797282026-03-04 22:40:25.489180131.013333123.067778744464220160.014572VINCIFrance134.150.006754-0.0542830.117451EUR
18191euro_stoxx_50SGO.PA2026-03-04Industrials1.0602880.9913971.0097730.5469120.9020924-0.3901770.8994520.8487400.276512-0.946419430.3608091.94737True0.530945130.162206192026-03-04 22:40:25.48918086.20200085.090222382567956480.007488SAINT GOBAINFrance77.16-0.011783-0.121185-0.112695EUR
19189euro_stoxx_50SAN.PA2026-03-04Healthcare0.7993670.6466150.7140661.0901750.8125567-0.4326980.9826030.9495470.285444-0.592341390.2621482.00000True0.170637200.130284202026-03-04 22:40:25.48918079.88966782.906444956733521920.018727SANOFIFrance79.23-0.007889-0.019309-0.042191EUR
# Polars — top scores
scores_pl.filter(pl.col("relative_value_score") > 0.8).head()
id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
i64strstrdatestrf64f64f64f64f64i64f64f64f64f64f64i64f64f64boolf64i64f64i64datetime[ns]f64f64i64f64strstrf64f64f64f64str
163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.26114null2.3889621.52116310.0161231.009091.1302640.0824860.477966160.1531571.84211false0.052711250.68394712026-03-04 22:40:25.48918092.08581.181889997512151040.019525BNP PARIBAS ACT.AFrance89.320.011437-0.0731560.105582EUR
196euro_stoxx_50VOW.DE2026-03-04Consumer Cyclical1.1662210.9402730.379831.5288911.0038042-0.2927480.9293920.9696280.180805-0.411969370.297071nullfalse0.555357120.38239762026-03-04 22:40:25.489180102.286667101.225556479238266880.009381VOLKSWAGEN AGGermany95.60.013786-0.048756-0.09039EUR
166euro_stoxx_50DG.PA2026-03-04Industrials0.7785730.8509850.9287971.1462680.9261563-0.0316971.0643531.0958090.0628710.59582110.0436082.04762false-0.538059340.32797282026-03-04 22:40:25.489180131.013333123.067778744464220160.014572VINCIFrance134.150.006754-0.0542830.117451EUR
191euro_stoxx_50SGO.PA2026-03-04Industrials1.0602880.9913971.0097730.5469120.9020924-0.3901770.8994520.848740.276512-0.946419430.3608091.94737true0.530945130.162206192026-03-04 22:40:25.48918086.20285.090222382567956480.007488SAINT GOBAINFrance77.16-0.011783-0.121185-0.112695EUR
189euro_stoxx_50SAN.PA2026-03-04Healthcare0.7993670.6466150.7140661.0901750.8125567-0.4326980.9826030.9495470.285444-0.592341390.2621482.0true0.170637200.130284202026-03-04 22:40:25.48918079.88966782.906444956733521920.018727SANOFIFrance79.23-0.007889-0.019309-0.042191EUR
# Polars — chain multiple filters
scores_pl.filter(
    pl.col("relative_value_score").is_not_null(),
    pl.col("relative_value_score") > 0.5,
).head()
id_indexsymbolscore_datesectorpe_zscorepb_zscoreev_ebitda_zscoreyield_zscorerelative_value_scorerelative_value_rankrelative_strengthsma_50_ratiosma_200_ratiodist_from_52w_highmomentum_scoremomentum_rankimplied_upsiderecommendation_meanprice_falling_analysts_bullishsentiment_scoresentiment_rankcomposite_scorecomposite_rank_scored_atsma_30_closesma_90_closemarket_capindex_weightshort_namecountrycurrent_priceday_change_pctfive_day_change_pctytd_change_pctcurrency
i64strstrdatestrf64f64f64f64f64i64f64f64f64f64f64i64f64f64boolf64i64f64i64datetime[ns]f64f64i64f64strstrf64f64f64f64str
163euro_stoxx_50BNP.PA2026-03-04Financial Services0.9133891.26114null2.3889621.52116310.0161231.009091.1302640.0824860.477966160.1531571.84211false0.052711250.68394712026-03-04 22:40:25.48918092.08581.181889997512151040.019525BNP PARIBAS ACT.AFrance89.320.011437-0.0731560.105582EUR
196euro_stoxx_50VOW.DE2026-03-04Consumer Cyclical1.1662210.9402730.379831.5288911.0038042-0.2927480.9293920.9696280.180805-0.411969370.297071nullfalse0.555357120.38239762026-03-04 22:40:25.489180102.286667101.225556479238266880.009381VOLKSWAGEN AGGermany95.60.013786-0.048756-0.09039EUR
194euro_stoxx_50TTE.PA2026-03-04Energy0.6911060.6093770.449610.7319480.62051120.04981.1091611.2125030.084110.91944450.0411312.04545false-0.542576350.33245972026-03-04 22:40:25.48918063.60358.2316671420039618560.027796TOTALENERGIESFrance66.86-0.018209-0.007570.202734EUR
166euro_stoxx_50DG.PA2026-03-04Industrials0.7785730.8509850.9287971.1462680.9261563-0.0316971.0643531.0958090.0628710.59582110.0436082.04762false-0.538059340.32797282026-03-04 22:40:25.489180131.013333123.067778744464220160.014572VINCIFrance134.150.006754-0.0542830.117451EUR
150euro_stoxx_50AD.AS2026-03-04Consumer Defensive0.7588060.3304730.7414011.05740.7220290.0354851.1553051.1706930.0083791.1355474-0.0149692.29412false-1.03108440.275495122026-03-04 22:40:25.48918037.24935.750667367344558080.00719KONINKLIJKE AHOLD DELHAIZE N.V…Netherlands41.420.0199460.0077860.187841EUR

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))
OperationPandasPolars
Single boolean filterdf[df["col"] > x]df.filter(pl.col("col") > x)
.loc with conditiondf.loc[mask]df.filter(mask_expr)
AND / OR / NOT(c1) & (c2), (c1) | (c2), ~cSame operators on expressions
query()df.query("col > 5")N/A — use filter expressions
isin / is_indf[df["col"].isin(lst)]df.filter(pl.col("col").is_in(lst))
between / is_betweendf[df["col"].between(a, b)]df.filter(pl.col("col").is_between(a, b))
Null checkdf[df["col"].isna()] / .notna()filter(pl.col("col").is_null()) / .is_not_null()
where / masks.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 / taildf.head(n) / df.tail(n)Same
Slicedf.iloc[a:b]df.slice(offset, length)
Sampledf.sample(n, random_state=42)df.sample(n, seed=42)
Unique rowsdf.drop_duplicates(subset=…)df.unique(subset=…)
Drop nullsdf.dropna(subset=…)df.drop_nulls(subset=…)
Sortdf.sort_values("col", ascending=False)df.sort("col", descending=True)
Multi-col sortsort_values(["a","b"], ascending=[T,F])sort(["a","b"], descending=[F,T])
Expression-level sortN/Apl.col("c").sort_by("d").over("g")
Semi-join filterdf[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 as pl.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, so df["col"] == value cannot 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 data

Reach for df["col"].isna() in Pandas and pl.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

.loc and .iloc do 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 .loc when the boundary values are labels you want included, and .iloc when 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 or seed= 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 and seed= in Polars for any sample that will be inspected, committed, tested, or compared across runs.

Python Explore, Select and Filter Recommendations

  1. Profile before transforming — run describe(), null_count(), value_counts(), and n_unique() on every dataset before writing any transformation logic. This takes seconds and prevents hours of debugging.
  2. 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.
  3. Use Polars selectors for type-based selectioncs.numeric(), cs.string(), cs.temporal() are safer than hard-coding column names, which break when schemas change.
  4. 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.
  5. Prefer is_in() over chained | conditionsdf.filter(pl.col("ticker").is_in(["A", "B", "C"])) is cleaner and faster than (col == "A") | (col == "B") | (col == "C").
  6. Always pass dropna=False in Pandas value_counts() — the default drops NaN, hiding the null count from your cardinality analysis.
  7. Sort before head()/tail() in unsorted datahead() on an unsorted DataFrame returns arbitrary rows, not the “first” in any meaningful order.

Troubleshooting and failure modes

SymptomLikely causeFix
KeyError on column selectionColumn name has whitespace or case mismatchdf.columns = df.columns.str.strip() then verify exact names
Filter returns empty DataFrame unexpectedlyFilter condition is too restrictive, or NaN rows excluded silentlyCheck value_counts(dropna=False) on the filter column
TypeError: Cannot perform 'rand_' with...Missing parentheses around boolean conditionsWrap each condition in (): (cond1) & (cond2)
.loc returns a Series instead of a DataFrameSelecting a single column with .loc[:, "col"]Use .loc[:, ["col"]] (list) to get a DataFrame
ColumnNotFoundError in PolarsColumn name does not exist or was renamed upstreamCheck df.columns or df.schema before the failing operation
describe() shows unexpected count < total rowsNon-numeric columns excluded by default (Pandas)Use describe(include="all") or target specific dtypes
sample() gives different results across runsNo seed setPass random_state=42 (Pandas) or seed=42 (Polars)
unique() returns unordered valuesBoth libraries return unique values in arbitrary orderChain .sort() after unique() if order matters
Polars filter() returns all rows unchangedExpression always evaluates to True (e.g., comparing wrong column)Print the boolean expression separately to verify: df.select(expr)
isin() returns all FalseList values don’t match column dtype (e.g., string “1” vs integer 1)Ensure the list values match the column dtype exactly