Passing DropDown Seleced value into python code to filter dataframe

What I’m trying to do: I am working on an app that shows various univariate and bivariate charts using features available in my data table. My set up is almost complete (~95%) barring one tiny yet major glitch I cannot seem to work my way out of. I want to be able to filter my dataframe based on a value selected in the dropdown. I read various topics, threads, documentation but somehow unable to solve my problem.

What I’ve tried and what’s not working: Currently my dropdown is getting populated using unique values from a column in my database. Now as a user makes a selection in the dropdown, I want all my charts to show only the data related to the selection. Code below for what I have tried.
My issue is I am unable to pass the selected weight value into my server side code to filter my data. Pretty sure its a simple fix (python newbie) but somehow I am stuck on how do I pass the selected drop down value to my server side code such that my dataframe gets filtered by it. Please note - I have around ~13 charts that will need to use the filtered data.

**Client side code*:
def drop_down_1_change(self, **event_args):
    weight = anvil.server.call('return_weight',self.drop_down_1.selected_value)
    return weight
 
**Server side code*
@anvil.server.callable
def brand_ct():
  def return_weight(weight):
    df = csv_to_df('cashew.csv')
    df2 = df[df["weight_updated"]==weight]  ###I am trying to filter data here using the selected value but it is not working
    return df2
    brand_ct= df2['Brand'].nunique()
    return brand_ct  ##I am using brand_ct in another function and it works fine if I use the original dataset instead of filtered data.

Here you define a function inside another function. Why?
I think you should get rid of def brand_ct(): and unindent the following line.

And then you return df2 and the code after that is never executed.

The comment says you successfully use brand_ct, but the brand_ct I see here does nothing. It just defines another function, does nothing with it and returns nothing.

Thank you for your comment. I see how my original statement around brand_ct might have lead to some confusion so I clarified more below.

Uncoupling the two functions, they look like this:

@anvil.server.callable
def return_weight(weight):
  df = csv_to_df('cashew.csv')
  df2 = df[df["weight_updated"]==weight]
  return df2

@anvil.server.callable
def brand_ct():
  brand_ct= df2['Brand'].nunique()
  return brand_ct

I need the filtered dataframe “df2” to be read by other functions such as brand_ct and many more. When I uncouple them,I get the error under function brand_ct to define df2. Error: NameError: name 'df2' is not defined

  • at ServerModule1, line 44 (where line 44 is → “brand_ct= df2[‘Brand’].nunique()” )

How do I pass my filtered dataframe “df2” to brand_ct and other such functions?

Note - brand_ct function counts the number of unique brands in the column “Brand” of my dataframe. I use that number further in a plot. The brand_ct part of my code worked as long as I use original data without trying to filter it per dropdown value aka when my function is below, it works:

@anvil.server.callable
def brand_ct():
  df = csv_to_df('cashew.csv')
  brand_ct= df['Brand'].nunique()
  return brand_ct

The short answer is you can’t. The scope of df2 is the function return_weight, so it is only accessible inside it.

One solution is this:

@anvil.server.callable
def return_weight(weight):
  df = csv_to_df('cashew.csv')
  return df[df["weight_updated"]==weight]

@anvil.server.callable
def brand_ct():
  df = csv_to_df('cashew.csv')
  return df['Brand'].nunique()

Or you could create a function that returns it, and, especially in case you call it many times, cache the value into a global variable. Something like this:

_df = None
def df():
  global _df # this is required in order to access global variables in read and write
  if _df is None:
    _df = csv_to_df('cashew.csv')
  return _df

@anvil.server.callable
def return_weight(weight):
  return df()[df["weight_updated"]==weight]

@anvil.server.callable
def brand_ct():
  return df()['Brand'].nunique()

The caching in the code above is useless, because a new interpreter is started every time the callables are called, so the the globals need to be regenerated anyway. But if you upgrade to a higher plan that allows to keep the server running, then the globals may (don’t rely on it, they just may) persist across calls.

Thanks again Stefano. I still do need a solution that helps me pass the drop down selected value into a python code so that I can filter my dataframe.

Your global variable gave me an idea, I tried it and it did not work. Let me keep hunting around…ugh, so close yet so far!

Your client side code is doing the right thing. It’s passing the value to the server side, where something happens (your filtering?) and is returned to the client. Then the client has a simple return weight, which returns whatever the server returned to… no one.

Instead of returning weight, it should update the UI. If your app already knows how to update the UI when the form is opened, you can use the same code here. The clean way would be to create an update_charts() function and call it from both the form_show and the drop_down_1_change methods.

If the idea is to use the server global from the client side, it is not going to work. The server and the client are running two independent Python interpreters, the server in England, the client in your computer. Thanks to the Anvil magic, the client can call functions on the server, send and fetch data, but can’t directly access server globals.

Another thing to keep in mind is that there are restrictions on what kind of data you can transfer across the server/client boundary. You can transfer numbers, strings, anything that fits inside a json object and a little more, but not everything. You can pass datetime objects, which wouldn’t fit inside standard json objects, you can pass database rows and search iterators, images, etc.

But I don’t know if you can pass a dataframe, I have never tried. If the Anvil guys thought that it’s a very common data structure, they may have added the automatic management of a dataframe transfer. Otherwise you will need to convert it first to some other data structure, like a list or dictionary.

2 Likes

Hi @lakshx413,

Zooming out a little, if we imagine that your app doesn’t need filtering, how are you getting the data from the server to the client right now for display? I assume that part is already working.

If I had to guess, I would expect that you are calling a Server Function in the startup code of your form, and that function returns the data (or even the full plots). This is exactly the right approach.

To support interactive filtering, I would recommend:

  1. Modify the Server Function you’re already calling so that it takes arguments specifying the various filters. Do the actual filtering inside this Server Function, and return the filtered data.
  2. When the user chooses a value from the dropdown, re-fetch the data (or plots) by calling the same Server Function again with the new filter values, and update your form.

Of course, when you call the Server Function to get data the first time (when the form loads), the filters will (probably) be blank. Each time you choose something new from the dropdown, you will update the plots to display the new, filtered data.

I hope this makes sense and helps! Please let me know if you’d like clarification of any of the above.

1 Like