0

I need to create messages of the kind Hi John, it's time to eat quantity_1 of food1, quantity_2 of food_2 ..., qunatity_n of food_n. For that I get pandas dataframes, that update every once in a while. For example the dataframes look sometimes like df1=

qantity,food
1,apple

and sometimes like df2=

quantity,food
1,apple
3,salads
2,carrots

I need with every new update to create out of the dataframe a string for a message. For df1 an f-string works neetly and for creating my desired message of Hi John, it's time to eat 1 apple. I can do:

f"Hi John, it's time to eat {df.quantity} {df1.food}

In the cases when I have something like df2, and I don't know explicitly how long df2 is, I would like to have a message of the kind Hi John, it's time to eat 1 apple, 3 salads, 2 carrots.

How can I create such a string? I thought of using the "splat" operator for something like join(*zip(df.quantity, df.food)), but I haven't figured it out. tnx

martineau
  • 119,623
  • 25
  • 170
  • 301
NeStack
  • 1,739
  • 1
  • 20
  • 40
  • you need to convert df2 into dict then loop through keys and values. –  Oct 01 '20 at 17:40

5 Answers5

2

Try this:

result=','.join([str(i[0])+' '+i[1] for i in zip(df.quantity, df.food)])

print(result)

'1 apple, 2 salads, 3 carrots'

And you can add this to get the final result:

"Hi John, it's time to eat " + result

Hi John, it's time to eat 1 apple, 2 salads, 3 carrots
IoaTzimas
  • 10,538
  • 2
  • 13
  • 30
  • 1
    Looks even nicer with an f-string: `result=','.join([f'{i[0]} {i[1]}' for i in zip(df.quantity, df.food)])` or `result = ', '.join(f'{quantity} {food}' for quantity, food in zip(df.quantity, df.food))` – Matthias Oct 01 '20 at 18:14
  • I like the brevity of the suggested solution, it is easy to read and see what happens, so I accept it as a solution! – NeStack Oct 01 '20 at 21:06
  • actually I tested it just now and I get the error message `TypeError: zip argument #1 must support iteration`. DO you know how to fix it? – NeStack Oct 02 '20 at 09:39
  • Strange. What is the length of your df? Maybe zip wouldn't work with len=1. Can you post first 3 rows of your df? – IoaTzimas Oct 02 '20 at 09:46
  • 1
    Sorry, I figured out the problem - I used the df from Siva's answer: `df1 = pd.DataFrame({'size':['1','2'], 'Food':['apple', 'banana']})`. The error came from the unfortunate column name `size`, because df.size was not treated as one of the columns, but as a method. Sorry again, it works in the general case – NeStack Oct 02 '20 at 10:27
1

There are two ways to approach this. The first option is to create a message column in the dataframe

df = pd.DataFrame(data={'quantity':  [1],'food': ['apple']})
df['message'] = df.apply(lambda x: f"Hi John, it's time to eat {x.quantity} {x.food}", axis = 1)
print(df['message'])

the second option is to get slice the dataframe object by index to create your messages outside of the dataframe

f"Hi John, it's time to eat {df.quantity[0]} {df.food[0]}"

to handle multiple records in the dataframe, you can iterate through the rows

"Hi John, it's time to eat " + ", ".join(list((f"{df.quantity[i]} {df.food[i]}" for i in df.index)))
Concorde
  • 87
  • 3
  • Thanks, but this only works for the case of `df1`, for the case of `df2` with more than 1 row your suggestion doesn't deliver the desired output. Any suggestions for improvement? – NeStack Oct 01 '20 at 17:51
  • 1
    for mulitle records, you can use this `"Hi John, it's time to eat " + ", ".join(list((f"{df.quantity[i]} {df.food[i]}" for i in df.index))) ` – Concorde Oct 01 '20 at 17:52
1

Try this

df1 = pd.DataFrame({'size':['1','2'], 'Food':['apple', 'banana']})
l_1 = [x + '' + y for x, y in zip(df1['size'], df1['Food'])]
"Hi John, it's time to eat " + ", ".join(l_1)
1
import pandas as pd 
df = pd.DataFrame([[1, 'apple'], [2, 'salads'], [5, 'carrots']], columns=['quantity','food'])
menu =  ', '.join(f"{quantity} {food}" for idx, (quantity, food) in df.iterrows())
print(f"Hi John, it's time to eat {menu}.")

output

Hi John, it's time to eat 1 apple, 2 salads, 5 carrots.

using package inflect you can do it with better grammar:

import inflect

p=inflect.engine()
menu = p.join([f"{quantity} {food}" for idx, (quantity, food) in df.iterrows()])
print(f"Hi John, it's time to eat {menu}.")

output:

Hi John, it's time to eat 1 apple, 2 salads, and 5 carrots.

inflect even can construct correct singular/plural form

buran
  • 13,682
  • 10
  • 36
  • 61
  • I like the iterrows use! – NeStack Oct 01 '20 at 21:05
  • I edited the answer from `idx, (qunaity, food) in df.iterrows()` to `idx, row in df.iterrows()`, so that the code works in a more general case, than the one with my dummy `df2` where I have only 2 columns – NeStack Oct 02 '20 at 10:31
  • please, don't edit my code. variable names used are descriptive, as per your question - quantity and food. it's not the `row` (i.e. row suggests all values in the respective row). I revert your edit. – buran Oct 02 '20 at 10:52
  • I don't know about the programming jargon of descriptive and respective, but I can tell you that the edits that I made serve the intention of my task better. And the intention is why I have written the question in the first place. The dataframe `df2` I have posted, and you refer to, is just a dummy. For my real task I logically use a df with many more columns and your code doesn't work with this real df, while my edits work with it. Furthermore, my edits would also suit the needs of future readers, that find this post, and don't have a df with just 2 columns. Поздрави :) – NeStack Oct 02 '20 at 11:23
  • Variable names that I use are consistent with your question. We all understand that it's a dummy DataFrame. You show dummy DataFrame with first column `quantity` and second column `food` and that's why I use these names. We don't care how you will adapt the solution for your real data. – buran Oct 02 '20 at 11:30
  • Maybe I am missing something - do you get reputation subtracted if your answer gets edited, or similar? Sorry in such a case. I see what you mean and your solution works for what is explicitly written. But I don't think this serves the main idea of stackoverflow (and my actual need) - solving technical problems. You instead insist of being (unnecessarily) stiff to the question presented. "We don't care how you will adapt the solution" - that's a pitty, other users with your reputation step in the shoes of the asker. Being curious: are you an authors group or why do you say "we"? thanks anyways – NeStack Oct 02 '20 at 11:57
1

I prefer to use str.format() for complex scenarios as it makes the code easier to read.

In this case:

import pandas as pd
df2=pd.DataFrame({'quantity':[1,3,2],'food':['apple','salads','carrots']})
def create_advice(df):
    s="Hi John, it's time to eat "+", ".join(['{} {}'.format(row['quantity'],row['food']) for index,row in df.iterrows()])+'.'
    return s

create_advice(df2)
>"Hi John, it's time to eat 1 apple, 3 salads, 2 carrots."

You might also want to modify the df slightly before creating the strings:

list_of_portioned_food=['salads']
df2['food']=df2.apply(lambda row: ('portions of ' if row['food'] in list_of_portioned_food else '')+row['food'],axis=1)
df2.iloc[len(df2)-1,1]='and '+str(df2.iloc[len(df2)-1,1])

applying the above function again:

create_advice(df2)
> "Hi John, it's time to eat 1 apple, 3 portions of salads, and 2 carrots."