Download upload csv

Download this notebook from GitHub (right-click to download).


Fileinput Exploration with Pandas and Plotly

In Panel the FileDownload widget allows downloading a file generated on the server the app is running on while the FileInput widget allows uploading a file. In this example we demonstrate a pipeline of two little apps one which allows generating a sample data CSV file and one which allows uploading this file and displays it as a Plotly plot.

For more details on how to use these components see FileInput and FileDownload reference guides.

Authors: Marc Skov Madsen and Philipp Rudiger

In [ ]:
import io
import param
import panel as pn
import pandas as pd
import random

from datetime import datetime, timedelta

import plotly.express as px

pn.extension('plotly')

Lets start out by creating some sample data by defining some parameters which declare bounds on the values to generate along with a FileDownload widget which will allow the user to download the data onto their machine.

In [ ]:
class SampleDataApp(param.Parameterized):
    
    samples = param.Integer(default=40, bounds=(0,100), doc="""
      Number of data samples to generate.""")
    
    voltage_bounds=param.Range(default=(0,100), bounds=(0,1000), doc="""
      The bounds of the voltage values to generate.""")

    time_bounds=param.CalendarDateRange(
        default=(datetime(2020, 2, 1), datetime(2020, 2, 26)),
        bounds=(datetime(2020, 1, 1), datetime(2020, 3, 26)),
        doc="The bounds of the time values to generate.")
    
    fub_ids = param.ListSelector(
        default=["a1", "b1", "b2"], objects=["a1", "b1", "b2"], doc="""
      The IDS to generate.""")
    
    sample_df = param.DataFrame(doc="""
      The current dataframe of samples.""")
    
    generate_sample_df = param.Action(lambda self: self.update_sample_df(), doc="""
      An action callback which will update the sample_df.""")
    
    file_name = param.String(default="sample_data.csv", doc="""
      The filename to save to.""")
        
    def __init__(self, **params):
        super().__init__(**params)
        self.update_sample_df()
        self.download = pn.widgets.FileDownload(filename=self.file_name,
                                                callback=self._download_callback)

    @pn.depends('file_name', watch=True)
    def _update_filename(self):
        self.download.filename = self.file_name

    def _download_callback(self):
        """
        A FileDownload callback will return a file-like object which can be serialized
        and sent to the client.
        """
        self.download.filename = self.file_name
        sio = io.StringIO()
        self.sample_df.to_csv(sio, index=False)
        sio.seek(0)
        return sio
        
    def update_sample_df(self, event=None):
        start = self.time_bounds[0]
        end = self.time_bounds[1]
        days = (end-start).days
        
        sample_data = {
            "Time": [start+timedelta(days=random.uniform(0,days)) for _ in range(0,self.samples)],
            "Voltage": [random.uniform(*self.voltage_bounds) for _ in range(0,self.samples)],
            "FubId": [random.choice(self.fub_ids) for _ in range(0,self.samples)],
        }
        self.sample_df = pd.DataFrame(sample_data) 
        
    
    def save_sample_data(self, event=None):
        if not self.sample_df is None:
            self.sample_df
            
    def view(self):
        return pn.Column(
            pn.Row(
                pn.Param(self, parameters=['samples', 'voltage_bounds', 'time_bounds']),
                pn.Column(self.param.generate_sample_df, self.param.file_name, self.download, align='end')
            ),
            self.param.sample_df,
        )

sample_data_app = SampleDataApp()
sample_data_app.view()

Click the Save sample df button

This should save the dataframe to your default download folder. Now let us define the VoltageApp which will display the data we just generated.

In [ ]:
class VoltageApp(param.Parameterized):
    data = param.DataFrame()
    
    file_input = param.Parameter()
    
    def __init__(self, **params):
        self.param.file_input.default = pn.widgets.FileInput()
        super().__init__(**params)
        self.plotly_pane = pn.pane.Plotly(height=400, sizing_mode="stretch_width")

    @pn.depends("file_input.value", watch=True)
    def _parse_file_input(self):
        value = self.file_input.value
        string_io = io.StringIO(value.decode("utf8"))
        self.data = pd.read_csv(string_io, parse_dates=["Time"])

    @pn.depends('data', watch=True)
    def get_plot(self):
        df = self.data
        if df is None:
            return
        assert ("Voltage" in df.columns) and ("Time" in df.columns), "no columns voltage and time"
        df = (df.loc[df['Voltage'] != 'Invalid/Calib']).copy(deep=True)
        df['Voltage'] = df['Voltage'].astype(float)
        if "FubId" in df.columns:
            p = px.scatter(df, x="Time", y="Voltage", color="FubId")
        else:
            p = px.scatter(df, x="Time", y="Voltage")
        self.plotly_pane.object = p
        
    def view(self):
        return pn.Column(
            self.file_input,
            self.plotly_pane,
        )
    
voltage_app = VoltageApp()

voltage_app.view()

Now let us put these two components together into a servable app:

In [ ]:
sample_data_app = SampleDataApp()
voltage_app = VoltageApp()

description = """
This application demonstrates the ability to download a file using the FileDownload widget 
and uploading a file using the FileInput widget.
</br></br>
Try filtering the data on the left, download the file by clicking on the Download button
and then plot it on the right by uploading that same file.
"""

pn.Row(
    sample_data_app.view(),
    pn.Column(
        description,
        voltage_app.view(),
    ),
    sizing_mode='stretch_both'
).servable()

Download this notebook from GitHub (right-click to download).