Skip to content

Commit 874ddb6

Browse files
committed
Adding files for Australian Wine Visualization Article
1 parent f0e237e commit 874ddb6

File tree

3 files changed

+3080
-0
lines changed

3 files changed

+3080
-0
lines changed

code/winepicker.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
""" Interactive Wine Visualization Tool
2+
3+
See pbpython.com for the associated blog post explaining the process and
4+
goals of this script
5+
"""
6+
7+
import pandas as pd
8+
from bokeh.plotting import figure
9+
from bokeh.layouts import layout, widgetbox
10+
from bokeh.models import ColumnDataSource, HoverTool, BoxZoomTool, ResetTool, PanTool
11+
from bokeh.models.widgets import Slider, Select, TextInput, Div
12+
from bokeh.models import WheelZoomTool, SaveTool, LassoSelectTool
13+
from bokeh.io import curdoc
14+
from functools import lru_cache
15+
16+
17+
# Define a cached function to read in the CSV file and return a dataframe
18+
@lru_cache()
19+
def load_data():
20+
df = pd.read_csv("Aussie_Wines_Plotting.csv", index_col=0)
21+
return df
22+
23+
# Column order for displaying the details of a specific review
24+
col_order = ['price', 'points', 'variety', 'province', 'description']
25+
26+
all_provinces = [
27+
"All", "South Australia", "Victoria", "Western Australia",
28+
"Australia Other", "New South Wales", "Tasmania"
29+
]
30+
31+
# Setup the display portions including widgets as well as text and HTML
32+
desc = Div(text="All Provinces", width=800)
33+
province = Select(title="Province", options=all_provinces, value="All")
34+
price_max = Slider(start=0, end=900, step=5, value=200, title="Maximum Price")
35+
title = TextInput(title="Title Contains")
36+
details = Div(text='Selection Details:', width=800)
37+
38+
# Populate the data with the dataframe
39+
source = ColumnDataSource(data=load_data())
40+
41+
# Build out the hover tools
42+
hover = HoverTool(tooltips=[
43+
("title", "@title"),
44+
("variety", "@variety"),
45+
])
46+
47+
# Define the tool list as a list of the objects so it is easier to customize
48+
# each object
49+
TOOLS = [
50+
hover, BoxZoomTool(), LassoSelectTool(), WheelZoomTool(), PanTool(),
51+
ResetTool(), SaveTool()
52+
]
53+
54+
# Build out the figure with the individual circle plots
55+
p = figure(
56+
plot_height=600,
57+
plot_width=700,
58+
title="Australian Wine Analysis",
59+
tools=TOOLS,
60+
x_axis_label="points",
61+
y_axis_label="price (USD)",
62+
toolbar_location='above')
63+
64+
p.circle(
65+
y="price",
66+
x='points',
67+
source=source,
68+
color='variety_color',
69+
size=7,
70+
alpha=0.4)
71+
72+
73+
# Define the functions to update the data based on a selection or change
74+
75+
def select_reviews():
76+
""" Use the current selections to determine which filters to apply to the
77+
data. Return a dataframe of the selected data
78+
"""
79+
df = load_data()
80+
81+
# Determine what has been selected for each widgetd
82+
max_price = price_max.value
83+
province_val = province.value
84+
title_val = title.value
85+
86+
# Filter by price and province
87+
if province_val == "All":
88+
selected = df[df.price <= max_price]
89+
else:
90+
selected = df[(df.province == province_val) & (df.price <= max_price)]
91+
92+
# Further filter by string in title if it is provided
93+
if title_val != "":
94+
selected = selected[selected.title.str.contains(title_val) == True]
95+
96+
# Example showing how to update the description
97+
desc.text = "Province: {} and Price < {}".format(province_val, max_price)
98+
return selected
99+
100+
101+
def update():
102+
""" Get the selected data and update the data in the source
103+
"""
104+
df_active = select_reviews()
105+
source.data = ColumnDataSource(data=df_active).data
106+
107+
108+
def selection_change(attrname, old, new):
109+
""" Function will be called when the poly select (or other selection tool)
110+
is used. Determine which items are selected and show the details below
111+
the graph
112+
"""
113+
selected = source.selected['1d']['indices']
114+
115+
# Need to get a list of the active reviews so the indices will match up
116+
df_active = select_reviews()
117+
118+
# If something is selected, then get those details and format the results
119+
# as an HTML table
120+
if selected:
121+
data = df_active.iloc[selected, :]
122+
temp = data.set_index('title').T.reindex(index=col_order)
123+
details.text = temp.style.render()
124+
else:
125+
details.text = "Selection Details"
126+
127+
128+
# Setup functions for each control so that changes will be captured and data
129+
# updated as required
130+
controls = [province, price_max, title]
131+
132+
for control in controls:
133+
control.on_change('value', lambda attr, old, new: update())
134+
135+
# If the source is changed to a selection, execute that selection process
136+
source.on_change('selected', selection_change)
137+
138+
# The final portion is to layout the parts and get the server going
139+
140+
# Build a box for all the controls
141+
inputs = widgetbox(*controls, sizing_mode='fixed')
142+
143+
# Define a simple layout
144+
l = layout([[desc], [inputs, p], [details]], sizing_mode='fixed')
145+
146+
# Update the data and instantiate the service
147+
update()
148+
curdoc().add_root(l)
149+
150+
# Show the title in the browser bar
151+
curdoc().title = "Australian Wine Analysis"

0 commit comments

Comments
 (0)