Question: I need to abbreviate a series or expression of phrases by extracting the capitalized words and then creating an abbreviation based on their proportional lengths. Here's what I'm trying to achieve:  Extract capitalized words from each phrase. Calculate proportional lengths based on the total length of the capitalized words in each phrase. Adjust the lengths to ensure the abbreviation meets a target length (e.g., 4 characters).  TODO: If the abbreviation results in duplicates, I need to either:  Automatically resolve them (e.g., by adding numbers or modifying characters) Flag them with a warning.  Currently, I’m using a Python function and mapping it across a Polars Series. Is there a more efficient way to do it using Polars built-in methods. Here's my current approach: import polars as pl def _abbreviate_phrase(phrase: str, length: int) -> str: """Abbreviate a single phrase by a constant length. The function aims to abbreviate phrases into a constant length by focusing on capitalized words and adjusting them according to their proportional lengths. Example: phrase = 'Commercial & Professional' length = 4 res = _abbreviate_phrase(phrase, length) print(res) # CoPr """ # determine size of slices capitalized_words = [word for word in phrase.split(' ') if word[0].isupper()] word_lengths = [len(word) for word in capitalized_words] total_word_length = sum(word_lengths) if total_word_length == 0: return '' # Return empty if no capitalized words proportional_lengths = [round(wl / total_word_length * length) for wl in word_lengths] total_proportional_length = sum(proportional_lengths) # Adjust slices if their total length doesn't match target length if total_proportional_length < length: for i in range(length - total_proportional_length): proportional_lengths[i] += 1 elif total_proportional_length > length: for i in range(total_proportional_length - length): proportional_lengths[i] -= 1 # Combine the abbreviated words and return the result abbreviated_phrase = ''.join([word[:plength] for word, plength in zip(capitalized_words, proportional_lengths)]) return abbreviated_phrase def abbreviate_phrases(phrases: pl.Series, length: int) -> pl.Series: """Abbreviate phrases by a constant length. Example: phrases = pl.Series([ 'Sunshine', 'Sunset', 'Climate Change and Environmental Impact', 'Health and Wellness', 'Quantum Computing and Physics', 'Global Warming and Renewable Resources' ]) length = 4 res = abbreviate_phrases(phrases, length) print(res) # Series: '' [str] # [ # "Suns" # "Suns" # "CEnI" # "HeWe" # "QCoP" # "GWRR" # ] """ abbreviates = phrases.map_elements(lambda x: _abbreviate_phrase(x, length), return_dtype=pl.String) # if not abbreviates.is_unique().all(): # print('WARNING: There are duplicated abbreviations.') return abbreviates  Edit: performance comparison setup for proposed solutions import sys import timeit def generate_phrases(n: int) -> pl.DataFrame: # Repeat the original phrases 'n' times phrases = pl.DataFrame({ "p": ['Climate Change and Environmental Impact', 'Health and Wellness', 'Quantum Computing and Physics', 'Global Warming and Renewable Resources', 'no capital letters'] }) return pl.concat([phrases.with_columns(pl.col('p')+'_'+str(i)) for i in range(n)]) def compare_performance(n: int, length: int = 4): phrases = generate_phrases(n) abbreviate_phrases_time = timeit.timeit(lambda: abbreviate_phrases(phrases['p'], length), number=10) abbreviate_phrases_harbeck_time = timeit.timeit(lambda: abbreviate_phrases_harbeck(phrases, phrase_column="p", length=length), number=10) abbreviate_phrases_jqurious_time = timeit.timeit(lambda: abbreviate_phrases_jqurious(phrases['p'], length=length), number=10) abbreviate_phrases_rle_time = timeit.timeit(lambda: abbreviate_phrases_rle(phrases['p'], length=length), number=10) ratio_harbeck = abbreviate_phrases_time / abbreviate_phrases_harbeck_time ratio_jqurious = abbreviate_phrases_time / abbreviate_phrases_jqurious_time ratio_rle = abbreviate_phrases_time / abbreviate_phrases_rle_time return ratio_harbeck, ratio_jqurious, ratio_rle  Edit: performance comparison results for proposed solutions n = 200_000 ratio_harbeck, ratio_jqurious, ratio_rle = compare_performance(n=n, length=4) print(f"Performance ratio ({n*5} rows):") print(f" original/harbeck {ratio_harbeck:.2f}x") print(f" original/jqurious {ratio_jqurious:.2f}x") print(f" original/rle {ratio_rle:.2f}x") print() print(f'python {sys.version.split(' ')[0]}') print(f'polars {pl.__version__}') # Performance ratio (1000000 rows): # original/harbeck 0.70x # original/jqurious 1.30x # original/rle 1.79x # python 3.12.0 # polars 1.12.0 

Answer: It is possible, but it's probably not an ideal task to perform this way. This is the fastest approach I've found so far. It runs ~2.5x faster for me with 1_000_000 phrases. (1.5s vs 3.9s) def abbreviate_phrases(s: pl.Series, length: int = 4): phrases = pl.col(s.name) is_upper = pl.element().filter(pl.element().str.contains(r"^\p{Upper}")) words = phrases.list.eval(is_upper) len_chars = phrases.list.eval(is_upper.str.len_chars()) total_len = len_chars.list.sum() proportion_len = ( (len_chars / total_len * length) .list.eval(pl.element().round().cast(int)) .alias("proportion_len") ) total_proportion_len = ( (pl.col("proportion_len").list.sum() - length) .alias("total_proportion_len") ) adjustments = ( pl.concat_list( (-pl.col("total_proportion_len")) .repeat_by(pl.col("total_proportion_len").abs()), pl.lit(0) .repeat_by( pl.col("proportion_len").list.len() - (pl.col("total_proportion_len").abs()) ) ) ) return ( s .to_frame() .with_row_index() .with_columns(phrases.str.split(" ")) .with_columns(words, proportion_len) .with_columns(total_proportion_len) .with_columns(pl.col("proportion_len") + adjustments) .explode(pl.exclude("index", "total_proportion_len")) .with_columns(phrases.str.slice(0, pl.col("proportion_len"))) .group_by("index", maintain_order=True) .agg(phrases) .select(phrases.list.join("")) .to_series() )  Some observations:  adjustments seems to be the most expensive operation and takes ~50% of the runtime.  .agg().select(phrases.list.join("")) was considerably faster than .agg(phrases.str.join()) which was surprising.    Update: Slightly faster version of adjustments using .rle() (this goes from 1.5s to 1.0s for me) (inspired by the when/then approach from @HenryHarbeck's answer) def abbreviate_phrases_rle(s: pl.Series, length = 4): phrases = pl.col(s.name) is_upper = pl.element().filter(pl.element().str.contains(r"^\p{Upper}")) words = phrases.list.eval(is_upper) word_lengths = phrases.list.eval(is_upper.str.len_chars()) total_length = word_lengths.list.sum() proportional_length = ( (word_lengths / total_length * length) .list.eval(pl.element().round().cast(int)) .alias("proportional_length") ) length_diff = ( (pl.col("proportional_length").list.sum() - length) .alias("length_diff") ) rle = ( pl.int_ranges(pl.col("index").rle().struct.field("len")) .flatten() ) adjustments = ( pl.when( pl.col("length_diff") != 0, pl.col("length_diff").abs() > rle ) .then(pl.when(pl.col("length_diff") < 0).then(1).otherwise(-1)) .otherwise(0) ) return ( s .to_frame() .with_row_index() .with_columns(phrases.str.split(" ")) .with_columns(words, proportional_length) .with_columns(length_diff) .explode(pl.exclude("index", "length_diff")) .with_columns(phrases.str.slice(0, pl.col("proportional_length") + adjustments)) .group_by("index", maintain_order=True) .agg(phrases) .select(phrases.list.join("")) .to_series() )  Polars plugins To do this "really" efficiently, you'd likely need to implement it in Rust as a Polars Plugin. If I take the parallel pig latinify example as a template. (I'm a Rust beginner, so may have missed some things.) use polars_utils::cache::FastFixedCache; use pyo3_polars::export::polars_core::export::regex::Regex; fn abbreviate_str(value: &str, reg: &mut Regex, output: &mut String) { let max_len = 4; let mut total_len = 0; let mut word_lengths = vec![]; let mut words = vec![]; if value.len() > 0 { for word in value.split_whitespace() { if reg.is_match(word) { let word_len = word.chars().count(); total_len += word_len; word_lengths.push(word_len); words.push(word); } } let mut total_proportional_len = 0; let mut proportional_lengths: Vec<_> = word_lengths .iter() .map(|wl| { let prop_len = (*wl as f64 / total_len as f64 * max_len as f64).round() as usize; total_proportional_len += prop_len; prop_len }) .collect(); if total_proportional_len > max_len { let diff = total_proportional_len - max_len; for idx in 0..diff { proportional_lengths[idx] -= 1 } } else if total_proportional_len < max_len { let diff = max_len - total_proportional_len; for idx in 0..diff { proportional_lengths[idx] += 1 } } words .iter() .zip(proportional_lengths.iter()) .for_each(|(word, prop_len)| { let end = match word.char_indices().map(|(idx, _)| idx).nth(*prop_len) { Some(end) => end, _ => word.len(), }; write!(output, "{}", &word[..end]) .unwrap() }) } } #[polars_expr(output_type=String)] fn abbreviate_with_parallelism( inputs: &[Series], context: CallerContext, //kwargs: PigLatinKwargs, ) -> PolarsResult<Series> { use rayon::prelude::*; let ca = inputs[0].str()?; let pat = r"^\p{Upper}"; if context.parallel() { let mut reg_cache = FastFixedCache::new(1); let out: StringChunked = ca.apply_into_string_amortized(|value, output| { let reg = reg_cache .try_get_or_insert_with(pat, |p| Regex::new(p)) .unwrap(); abbreviate_str(value, reg, output) }); Ok(out.into_series()) } else { POOL.install(|| { let n_threads = POOL.current_num_threads(); let splits = split_offsets(ca.len(), n_threads); let chunks: Vec<_> = splits .into_par_iter() .map(|(offset, len)| { let mut reg_cache = FastFixedCache::new(1); let sliced = ca.slice(offset as i64, len); let out = sliced.apply_into_string_amortized(|value, output| { let reg = reg_cache .try_get_or_insert_with(pat, |p| Regex::new(p)) .unwrap(); abbreviate_str(value, reg, output) }); out.downcast_iter().cloned().collect::<Vec<_>>() }) .collect(); Ok( StringChunked::from_chunk_iter(ca.name().clone(), chunks.into_iter().flatten()) .into_series(), ) }) } }  For comparison, this runs in 0.1s (for 10 million rows I get 1.1s vs 16.1s)  https://docs.pola.rs/user-guide/plugins/your-first-polars-plugin/  https://marcogorelli.github.io/polars-plugins-tutorial/stringify/