Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dash Plotly - How to go about solving IndexError: list index out of range', when only the data-source is altered?

Tags:

I am very new to python coding. With Dash - Plotly, I have plotted sensor data onto a GEO map, below the map is the histogram, which shows the frequency of sensor-observations per hour. There are three drop-down entries to filter the data based on date-picker from a calendar, a sensor picker and an hour picker.

The sample code of Dash-Plotly takes initially its own example CSV file for the image above (location: NY). I took my own CSV and made sure to alter it according to the format of the example CSV's (location: Africa). Which resulted into the following data format (two row sample):

**Date/Time          Lat                 Lon**
2019-03-25 04:00:00 -10,80948998827914  20,19160777427344  
2019-03-25 04:05:00 -10,798684405083584 20,16288145431259

My problem: Everything seems to work. Except when I select any date past (say) 7th of the month. Then I get three errors, which for the life of me I don't understand why, because nothing has changed except for the CSV:

EDIT 1: As an example use-case: in my CSV I have sensor observations for the date '2019-03-23'. So on the webpage when I select the date: March 23, 2019. I get the following errors:

- Callback error updating total-rides.children - IndexError: list index out of range

 File "/Frontend/app.py", line 262, in update_total_rides
    len(totalList[date_picked.month - 4][date_picked.day - 1])

- Callback error updating histogram.figure - IndexError: list index out of range

 File "/Frontend/app.py", line 322, in update_histogram
    [xVal, yVal, colorVal] = get_selection(monthPicked,dayPicked, selection) 

 File "/Users/Mieer/Desktop/DSP_Frontend/app.py", line 231, in **get_selection**
    yVal.append(len(totalList[month][day][totalList[month][day].index.hour== i]))

- Callback error updating map-graph.figure - IndexError: list index out of range

 File "/Frontend/app.py", line 419, in update_graph
    listCoords = getLatLonColor(selectedData, monthPicked,dayPicked) 

 File "/Users/Mieer/Desktop/DSP_Frontend/app.py", line 382, in **getLatLonColor**
    listCoords = totalList[month][day]

My Question: How can one solve the range issue of the list? As for all I know, all that was altered in the code was the datafile which is full of enough data of the right format as I benchmarked the example CSV for which everything worked.

EDIT 2: the code hasn't changed, only the CSV has been replaced with one that considers the exact same format, but with way less records. I have added the callback-graph:

Callback Grpah

Below my code:

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import numpy as np

from dash.dependencies import Input, Output
from plotly import graph_objs as go
from plotly.graph_objs import *
from datetime import datetime as dt


app = dash.Dash(
    __name__, meta_tags=[{"name": "viewport", "content": "width=device-width"}]
)
server = app.server


# Plotly mapbox public token
mapbox_access_token = "pk.eyJ1IjoicGxvdGx5bWFwYm94IiwiYSI6ImNqdnBvNDMyaTAxYzkzeW5ubWdpZ2VjbmMifQ.TXcBE-xg9BFdV2ocecc_7g"

list_of_fixed_sensors = {
    "sensor_1_2": {"lat": -10.736196, "lon": 20.060188},
    "sensor_1_3": {"lat": -10.736196, "lon": 20.106700},
    "sensor_1_6": {"lat": -10.736196, "lon": 20.246292},

# Initialize data frame
df1 = pd.read_csv(
    "/Users/ME/Desktop/Frontend/sensor_points.csv",
    dtype=object,
)
df = pd.concat([df1], axis=0)
df["Date/Time"] = pd.to_datetime(df["Date/Time"], format="%Y-%m-%d %H:%M")
df.index = df["Date/Time"]
df.drop("Date/Time", 1, inplace=True)
totalList = []
for month in df.groupby(df.index.month):
    dailyList = []
    for day in month[1].groupby(month[1].index.day):
        dailyList.append(day[1])
    totalList.append(dailyList)
totalList = np.array(totalList)

# Layout of Dash App HTML 
app.layout = html.Div(
    children=[
        html.Div(
            className="row",
            children=[
                # Column for user controls
                html.Div(
                    className="four columns div-user-controls",
                    children=[
                        html.Img(
                            className="logo", src=app.get_asset_url("dash-logo-new-.png")
                        ),
                        html.H2("DASHBOARD - Park Monitoring"),
                        html.Div(
                            className="div-for-dropdown",
                            children=[
                                dcc.DatePickerSingle(
                                    id="date-picker",
                                    min_date_allowed=dt(2019, 3, 1),
                                    max_date_allowed=dt(2019, 12, 31),
                                    initial_visible_month=dt(2019, 3, 1),
                                    date=dt(2019, 3, 1).date(),
                                    display_format="MMMM DD, YYYY",
                                    style={"border": "0px solid white"}
                                )
                            ],
                        ),
                        # Change to side-by-side for mobile layout
                        html.Div(
                            className="row",
                            children=[
                                html.Div(
                                    className="div-for-dropdown",
                                    children=[
                                        # Dropdown for locations on map
                                        dcc.Dropdown(
                                            id="location-dropdown",
                                            options=[
                                                {"label": i, "value": i}
                                                for i in list_of_fixed_sensors
                                            ],
                                            placeholder="Select a location",
                                        )
                                    ],
                                ),
                                html.Div(
                                    className="div-for-dropdown",
                                    children=[
                                        # Dropdown to select times
                                        dcc.Dropdown(
                                            id="bar-selector",
                                            options=[
                                                {
                                                    "label": str(n) + ":00",
                                                    "value": str(n),
                                                }
                                                for n in range(24)
                                            ],
                                            multi=True,
                                            placeholder="Select certain hours",
                                        )
                                    ],
                                ),
                            ],
                        ),
                        html.H1(id="total-rides"),
                        html.H1(id="total-rides-selection"),
                        html.H1(id="date-value"),
                    ],
                ),
                # Column for app graphs and plots
                html.Div(
                    className="eight columns div-for-charts bg-grey",
                    children=[
                        dcc.Graph(id="map-graph"),
                        html.Div(
                            className="text-padding",
                            children=[
                                "Select any of the bars on the histogram to section data by time."
                            ],
                        ),
                        dcc.Graph(id="histogram"),
                    ],
                ),
            ],
        )
    ]
)

# Get the amount of rides per hour based on the time selected
# This also higlights the color of the histogram bars based on
# if the hours are selected
def get_selection(month, day, selection):
    xVal = []
    yVal = []
    xSelected = []
    colorVal = [
        "#F4EC15",
        "#DAF017",
        "#BBEC19",
        "#9DE81B",
        "#80E41D",
        "#66E01F",
        "#4CDC20",
        "#34D822",
        "#24D249",
        "#25D042",
        "#26CC58",
        "#28C86D",
        "#29C481",
        "#2AC093",
        "#2BBCA4",
        "#2BB5B8",
        "#2C99B4",
        "#2D7EB0",
        "#2D65AC",
        "#2E4EA4",
        "#2E38A4",
        "#3B2FA0",
        "#4E2F9C",
        "#603099",
    ]

    # Put selected times into a list of numbers xSelected
    xSelected.extend([int(x) for x in selection])

    for i in range(24):
        # If bar is selected then color it white
        if i in xSelected and len(xSelected) < 24:
            colorVal[i] = "#FFFFFF"
        xVal.append(i)
        # Get the number of rides at a particular time
        yVal.append(len(totalList[month][day][totalList[month][day].index.hour == i]))
    return [np.array(xVal), np.array(yVal), np.array(colorVal)]


# Selected Data in the Histogram updates the Values in the DatePicker
@app.callback(
    Output("bar-selector", "value"),
    [Input("histogram", "selectedData"), Input("histogram", "clickData")],
)
def update_bar_selector(value, clickData):
    holder = []
    if clickData:
        holder.append(str(int(clickData["points"][0]["x"])))
    if value:
        for x in value["points"]:
            holder.append(str(int(x["x"])))
    return list(set(holder))


# Clear Selected Data if Click Data is used
@app.callback(Output("histogram", "selectedData"), [Input("histogram", "clickData")])
def update_selected_data(clickData):
    if clickData:
        return {"points": []}


# Update the total number of observations
@app.callback(Output("total-rides", "children"), [Input("date-picker", "date")])
def update_total_rides(datePicked):
    date_picked = dt.strptime(datePicked, "%Y-%m-%d")
    return "Total number of observations: {:,d}".format(
        len(totalList[date_picked.month - 4][date_picked.day - 1])
    )


# Update the total number of observations from selected bar in histogram
@app.callback(
    [Output("total-rides-selection", "children"), Output("date-value", "children")],
    [Input("date-picker", "date"), Input("bar-selector", "value")],
)
def update_total_rides_selection(datePicked, selection):
    firstOutput = ""

    if selection is not None or len(selection) is not 0:
        date_picked = dt.strptime(datePicked, "%Y-%m-%d")
        totalInSelection = 0
        for x in selection:
            totalInSelection += len(
                totalList[date_picked.month - 4][date_picked.day - 1][
                    totalList[date_picked.month - 4][date_picked.day - 1].index.hour
                    == int(x)
                ]
            )
        firstOutput = "Total observations for selected time: {:,d}".format(totalInSelection)

    if (
        datePicked is None
        or selection is None
        or len(selection) is 24
        or len(selection) is 0
    ):
        return firstOutput, (datePicked, " - showing hour(s): All")

    holder = sorted([int(x) for x in selection])

    if holder == list(range(min(holder), max(holder) + 1)):
        return (
            firstOutput,
            (
                datePicked,
                " - showing hour(s): ",
                holder[0],
                "-",
                holder[len(holder) - 1],
            ),
        )

    holder_to_string = ", ".join(str(x) for x in holder)
    return firstOutput, (datePicked, " - showing hour(s): ", holder_to_string)


# Update Histogram Figure based on Month, Day and Times Chosen
@app.callback(
    Output("histogram", "figure"),
    [Input("date-picker", "date"), Input("bar-selector", "value")],
)
def update_histogram(datePicked, selection):
    date_picked = dt.strptime(datePicked, "%Y-%m-%d")
    monthPicked = date_picked.month - 4
    dayPicked = date_picked.day - 1

    [xVal, yVal, colorVal] = get_selection(monthPicked, dayPicked, selection)

    layout = go.Layout(
        bargap=0.01,
        bargroupgap=0,
        barmode="group",
        margin=go.layout.Margin(l=10, r=0, t=0, b=50),
        showlegend=False,
        plot_bgcolor="#323130",
        paper_bgcolor="#323130",
        dragmode="select",
        font=dict(color="white"),
        xaxis=dict(
            range=[-0.5, 23.5],
            showgrid=False,
            nticks=25,
            fixedrange=True,
            ticksuffix=":00",
        ),
        yaxis=dict(
            range=[0, max(yVal) + max(yVal) / 4],
            showticklabels=False,
            showgrid=False,
            fixedrange=True,
            rangemode="nonnegative",
            zeroline=False,
        ),
        annotations=[
            dict(
                x=xi,
                y=yi,
                text=str(yi),
                xanchor="center",
                yanchor="bottom",
                showarrow=False,
                font=dict(color="white"),
            )
            for xi, yi in zip(xVal, yVal)
        ],
    )

    return go.Figure(
        data=[
            go.Bar(x=xVal, y=yVal, marker=dict(color=colorVal), hoverinfo="x"),
            go.Scatter(
                opacity=0,
                x=xVal,
                y=yVal / 2,
                hoverinfo="none",
                mode="markers",
                marker=dict(color="rgb(66, 134, 244, 0)", symbol="square", size=40),
                visible=True,
            ),
        ],
        layout=layout,
    )


# Get the Coordinates of the chosen months, dates and times
def getLatLonColor(selectedData, month, day):
    listCoords = totalList[month][day]

    # No times selected, output all times for chosen month and date
    if selectedData is None or len(selectedData) is 0:
        return listCoords
    listStr = "listCoords["
    for time in selectedData:
        if selectedData.index(time) is not len(selectedData) - 1:
            listStr += "(totalList[month][day].index.hour==" + str(int(time)) + ") | "
        else:
            listStr += "(totalList[month][day].index.hour==" + str(int(time)) + ")]"
    return eval(listStr)


# Update Map Graph based on date-picker, selected data on histogram and location dropdown
@app.callback(
    Output("map-graph", "figure"),
    [
        Input("date-picker", "date"),
        Input("bar-selector", "value"),
        Input("location-dropdown", "value"),
    ],
)
def update_graph(datePicked, selectedData, selectedLocation):
    zoom = 10.5
    latInitial = -10.736196
    lonInitial = 20.060188
    bearing = 0

    if selectedLocation:
        zoom = 13.0
        latInitial = list_of_fixed_sensors[selectedLocation]["lat"]
        lonInitial = list_of_fixed_sensors[selectedLocation]["lon"]

    date_picked = dt.strptime(datePicked, "%Y-%m-%d")
    monthPicked = date_picked.month - 4
    dayPicked = date_picked.day - 1
    listCoords = getLatLonColor(selectedData, monthPicked, dayPicked)

    return go.Figure(
        data=[
            # Data for all rides based on date and time
            Scattermapbox(
                lat=listCoords["Lat"],
                lon=listCoords["Lon"],
                mode="markers",
                hoverinfo="lat+lon+text",
                text=listCoords.index.hour,
                marker=dict(
                    showscale=True,
                    color=np.append(np.insert(listCoords.index.hour, 0, 0), 23),
                    opacity=0.5,
                    size=5,
                    colorscale=[
                        [0, "#F4EC15"],
                        [0.04167, "#DAF017"],
                        [0.0833, "#BBEC19"],
                        [0.125, "#9DE81B"],
                        [0.1667, "#80E41D"],
                        [0.2083, "#66E01F"],
                        [0.25, "#4CDC20"],
                        [0.292, "#34D822"],
                        [0.333, "#24D249"],
                        [0.375, "#25D042"],
                        [0.4167, "#26CC58"],
                        [0.4583, "#28C86D"],
                        [0.50, "#29C481"],
                        [0.54167, "#2AC093"],
                        [0.5833, "#2BBCA4"],
                        [1.0, "#613099"],
                    ],
                    colorbar=dict(
                        title="Time of<br>Day",
                        x=0.93,
                        xpad=0,
                        nticks=24,
                        tickfont=dict(color="#d8d8d8"),
                        titlefont=dict(color="#d8d8d8"),
                        thicknessmode="pixels",
                    ),
                ),
            ),
             # Plot of fixed sensors on the map
            Scattermapbox(
                lat=[list_of_fixed_sensors[i]["lat"] for i in list_of_fixed_sensors],
                lon=[list_of_fixed_sensors[i]["lon"] for i in list_of_fixed_sensors],
                mode="markers",
                marker=dict(size=8,  color='white', symbol='square', opacity=0.2),
                hoverinfo="text",
                text=[i for i in list_of_fixed_sensors],
            ),
        ],
        layout=Layout(
            autosize=True,
            margin=go.layout.Margin(l=0, r=35, t=0, b=0),
            showlegend=False,
            mapbox=dict(
                accesstoken=mapbox_access_token,
                center=dict(lat=latInitial, lon=lonInitial),
                style="dark",
                bearing=bearing,
                zoom=zoom,
            ),
            updatemenus=[
                dict(
                    buttons=(
                        [
                            dict(
                                args=[
                                    {
                                        "mapbox.zoom": 10.5,
                                        "mapbox.center.lon": "24.060188",
                                        "mapbox.center.lat": "-10.736196",
                                        "mapbox.bearing": 0,
                                        "mapbox.style": "dark",
                                    }
                                ],
                                label="Reset Zoom",
                                method="relayout",
                            )
                        ]
                    ),
                    direction="left",
                    pad={"r": 0, "t": 0, "b": 0, "l": 0},
                    showactive=False,
                    type="buttons",
                    x=0.45,
                    y=0.02,
                    xanchor="left",
                    yanchor="bottom",
                    bgcolor="#323130",
                    borderwidth=1,
                    bordercolor="#6d6d6d",
                    font=dict(color="#FFFFFF"),
                )
            ],
        ),
    )


if __name__ == "__main__":
    app.run_server(debug=True)
like image 497
MSD Avatar asked Jan 10 '20 22:01

MSD


1 Answers

As the error message suggests, all errors are caused by the index-out-of-range for the array totalList. The reason is quite obvious: totalList contains less elements (smaller shape) than you expect. See this section of your code:

totalList = []
for month in df.groupby(df.index.month):
    dailyList = []
    for day in month[1].groupby(month[1].index.day):
        dailyList.append(day[1])
    totalList.append(dailyList)
totalList = np.array(totalList)

It seems that you believe the value of month in the first loop will always iterate through 1, 2, ..., 12 and the day through 1,2,...,31. But those are not guaranteed. The shape of totalList strongly depends on the content of your input data (the CSV file). Let's say the file only contains records for 3 months (e.g. Jan. May and Dec.), then len(totalList)==3. More importantly, totalList[2] will contain the data for May instead of Feb.! So your code totalList[month][...] will give you the wrong data!! This is a more critical issue than those index-out-of-range errors.

You can get the data for a specific month and day by a single line:

df[(df.index.month==month) & (df.index.day==day)] # equivalent to totalList[month][day]

So it is not necessary to make use of the totalList array. I'd suggest you to refactor your code to get rid of this redundant data structure.

like image 141
gdlmx Avatar answered Sep 17 '22 11:09

gdlmx