4

I have a pandas dataframe with two columns:

df.selection
(...)
1454    5
1458    6
1473    4
1474    4
1487    4
1491    3
1500    6
Name: selection, Length: 117, dtype: int64

and

df.value_lsts
(...)
1454         [8.4, 16.0, 7.4, 3.96, 17.5, 2.6]
1458       [8.85, 3.25, 5.3, 4.95, 8.14, 11.0]
1473     [9.8, 5.28, 11.67, 15.15, 4.47, 3.06]
1474       [5.5, 2.19, 7.7, 11.98, 28.0, 8.54]
1487      [26.6, 9.74, 7.71, 6.46, 2.28, 7.58]
1491       [6.4, 3.1, 19.92, 4.2, 6.37, 11.05]
1500     [3.0, 22.91, 8.61, 13.58, 6.37, 3.69]
Name: value_lsts, Length: 117, dtype: object

That is a column of lists.

What I need is to create another column which value will be the given by:

value_lsts[df.selection - 1]

For example for row 1500 we have

df.value_lsts
1500     [3.0, 22.91, 8.61, 13.58, 6.37, 3.69]

df.selection
1500    6

so the return value would be 3.69

I have tried everything but could not come up with a solution. What is the pythonic way of accessing the correct index through the df.selection column ?

Thank you very much. Piero

Piero Costa
  • 180
  • 11
  • 1
    I've added solutions with better performance, that may be useful. If you still have any questions - feel free to ask. If no - I'll be thankful if you can accept any answer that satisfies you. – Stas Buzuluk Sep 01 '20 at 13:55

2 Answers2

4

Notice, that putting mutable objects inside a DataFrame can be an antipattern


If you are sure in what you are trying to achieve and confident that you need a column of lists - you can solve your problem like this:

  1. Using apply method:

    df["new_column"] = df.apply(lambda raw: raw.value_lsts[raw.selection -1], axis = 1)
    
  2. Using list comprehension:

    df["new_column"]  = [x[y-1] for x, y in zip(df['value_lsts'], df['selection'])]
    
  3. Using vectorized function:

    def get_by_index(value_lsts,selection): # you may use lambda here as well
        return value_lsts[selection-1]
    
    df["new_column"] = np.vectorize(get_by_index) (df['value_lsts'], df['selection'])
    

Which option to choose is a trade-off between readability and performance, in my opinion.


Let's compare algorithms performance

Create larger dataframe

df_1 = df.sample(100000, replace=True).reset_index(drop=True)

Timings

# 1. apply 
%timeit df_1["new_column"] = df_1.apply(lambda raw: raw.value_lsts[raw.selection-1], axis = 1)
2.77 s ± 94.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

# 2. list comprehension:
%timeit df_1["new_column"]  = [x[y-1] for x, y in zip(df_1['value_lsts'], df_1['selection'])] 
33.9 ms ± 1.29 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

# 3. vectorized function:
%timeit df_1["new_column"] = np.vectorize(get_by_index) (df_1['value_lsts'], df_1['selection'])
12 ms ± 302 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

# 4. solution proposed by @anky using lookup 
%%timeit 
u = pd.DataFrame(df_1['value_lsts'].tolist(),index=df_1.index) #helper dataframe
df_1['selected_value'] = u.lookup(u.index,df_1['selection']-1)
51.9 ms ± 865 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

If you aren't sure that you really need a column of lists - you can read about the proper way for splitting column of lists to multiple columns.

Stas Buzuluk
  • 794
  • 9
  • 19
2

You can alternatively make use of df.lookup here after converting the series of lists to a dataframe (Note that python indexing starts at 0 hence selection-1 should be used per your logic)

u = pd.DataFrame(df['value_list'].tolist(),index=df.index) #helper dataframe
df['selected_value'] = u.lookup(u.index,df['selection']-1)

print(df)

      selection                             value_list  selected_value
1454          5      [8.4, 16.0, 7.4, 3.96, 17.5, 2.6]           17.50
1458          6    [8.85, 3.25, 5.3, 4.95, 8.14, 11.0]           11.00
1473          4  [9.8, 5.28, 11.67, 15.15, 4.47, 3.06]           15.15
1474          4    [5.5, 2.19, 7.7, 11.98, 28.0, 8.54]           11.98
1487          4   [26.6, 9.74, 7.71, 6.46, 2.28, 7.58]            6.46
1491          3    [6.4, 3.1, 19.92, 4.2, 6.37, 11.05]           19.92
1500          6  [3.0, 22.91, 8.61, 13.58, 6.37, 3.69]            3.69
anky
  • 74,114
  • 11
  • 41
  • 70