Reputation: 103
I would like to remove the first 3 characters from strings in a Dataframe column where the length of the string is > 4
If else they should remain the same.
E.g
bloomberg_ticker_y
AIM9
DJEM9 # (should be M9)
FAM9
IXPM9 # (should be M9)
I can filter the strings by length:
merged['bloomberg_ticker_y'].str.len() > 4
and slice the strings:
merged['bloomberg_ticker_y'].str[-2:]
But not sure how to put this together and apply it to my dataframe
Any help would be appreciated.
Upvotes: 8
Views: 3408
Reputation: 42896
Saw a quite big variety of answers, so decided to compare them in terms of speed:
# Create big size test dataframe
df = pd.DataFrame({'bloomberg_ticker_y' : ['AIM9', 'DJEM9', 'FAM9', 'IXPM9']})
df = pd.concat([df]*100000)
df.shape
#Out
(400000, 1)
CS95 #1 np.where
%%timeit
np.where(df['bloomberg_ticker_y'].str.len() > 4,
df['bloomberg_ticker_y'].str[3:],
df['bloomberg_ticker_y'])
Result:
163 ms ± 12.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
CS95 #2 vectorized map
based solution
%%timeit
df['bloomberg_ticker_y'].map(lambda x: x[3:] if len(x) > 4 else x)
Result:
86 ms ± 7.31 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
Yatu DataFrame.mask
%%timeit
df.bloomberg_ticker_y.mask(df.bloomberg_ticker_y.str.len().gt(4),
other=df.bloomberg_ticker_y.str[-2:])
Result:
187 ms ± 18.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Vlemaistre list comprehension
%%timeit
[x[-2:] if len(x)>4 else x for x in df['bloomberg_ticker_y']]
Result:
84.8 ms ± 4.85 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
pault str.replace
with regex
%%timeit
df["bloomberg_ticker_y"].str.replace(r".{3,}(?=.{2}$)", "")
Result:
324 ms ± 17.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Cobra DataFrame.apply
%%timeit
df.apply(lambda x: (x['bloomberg_ticker_y'][3:] if len(x['bloomberg_ticker_y']) > 4 else x['bloomberg_ticker_y']) , axis=1)
Result:
6.83 s ± 387 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Conclusion
Fastest method is list comprehension
closely followed by vectorized map
based solution.
Slowest method is DataFrame.apply
by far (as expected) followed by str.replace
with regex
Upvotes: 5
Reputation: 200
You can also use DataFrame.apply :
import pandas as pd
df = pd.DataFrame({'bloomberg_ticker_y' : ['AIM9', 'DJEM9', 'FAM9', 'IXPM9']})
df['bloomberg_ticker_y'] = df.apply(lambda x: (x['bloomberg_ticker_y'][3:] if len(x['bloomberg_ticker_y']) > 4 else x['bloomberg_ticker_y']) , axis=1)
Output :
bloomberg_ticker_y
0 AIM9
1 M9
2 FAM9
3 M9
Upvotes: 3
Reputation: 43504
Another approach is to use regular expressions:
df["bloomberg_ticker_y"].str.replace(r".{3,}(?=.{2}$)", "")
#0 AIM9
#1 M9
#2 FAM9
#3 M9
The pattern means:
.{3,}
: Match 3 or more characters(?=.{2}$)
: Positive look ahead for exactly 2 characters followed by the end of the string.Upvotes: 2
Reputation: 88236
You can use DataFrame.mask
:
df['bloomberg_ticker_y'] = (df.bloomberg_ticker_y.mask(
df.bloomberg_ticker_y.str.len().gt(4),
other=df.bloomberg_ticker_y.str[-2:]))
bloomberg_ticker_y
0 AIM9
1 M9
2 FAM9
3 M9
Upvotes: 3
Reputation: 3331
You can use a list comprehension :
df = pd.DataFrame({'bloomberg_ticker_y' : ['AIM9', 'DJEM9', 'FAM9', 'IXPM9']})
df['new'] = [x[-2:] if len(x)>4 else x for x in df['bloomberg_ticker_y']]
Output :
bloomberg_ticker_y new
0 AIM9 AIM9
1 DJEM9 M9
2 FAM9 FAM9
3 IXPM9 M9
Upvotes: 9
Reputation: 402423
You can use numpy.where
to apply a condition to pick slices based on string length.
np.where(df['bloomberg_ticker_y'].str.len() > 4,
df['bloomberg_ticker_y'].str[3:],
df['bloomberg_ticker_y'])
# array(['AIM9', 'M9', 'FAM9', 'M9'], dtype=object)
df['bloomberg_ticker_sliced'] = (
np.where(df['bloomberg_ticker_y'].str.len() > 4,
df['bloomberg_ticker_y'].str[3:],
df['bloomberg_ticker_y']))
df
bloomberg_ticker_y bloomberg_ticker_sliced
0 AIM9 AIM9
1 DJEM9 M9
2 FAM9 FAM9
3 IXPM9 M9
If you fancy a vectorized map
based solution, it is
df['bloomberg_ticker_y'].map(lambda x: x[3:] if len(x) > 4 else x)
0 AIM9
1 M9
2 FAM9
3 M9
Name: bloomberg_ticker_y, dtype: object
Upvotes: 8