Public
Edited
Feb 14
4 forks
6 stars
Insert cell
# XYZ Hypothesis
Insert cell
Business idea analysis from [The Right It](https://www.youtube.com/watch?v=4sZMHAMN0DQ). Set your goal and hypothesis:
Insert cell
// Minimum market share
viewof xPercent = Inputs.range([0, 100], {
label: "At least",
step: 1,
value: urlParams.x ? Math.min(Math.max(parseInt(urlParams.x), 0), 100) : 20
})
Insert cell
// Target market
viewof y = Inputs.text({
label: "percent of",
value: urlParams.y || "Netflix DVD subscribers"
})
Insert cell
// Hypothesis about what Y will do
viewof z = Inputs.text({
label: "will",
value:
urlParams.z || "switch to a streaming-only subscription for $8.90 per month"
})
Insert cell
<span style="font-size: 0.8em;">_[Link to this hypothesis](${url})_</span>
Insert cell
---
Insert cell
## Results
Insert cell
At least ${Math.round(x * 100)}% of ${y} will ${z}.
Insert cell
viewof dataInputs = {
const obj = _.keyBy(_.range(0, maxExperimentCount), (i) => "experiment" + i);
const inputs = _.mapValues(obj, (i) => {
const value = urlParams["e" + (i + 1)]
? Math.min(Math.max(parseInt(urlParams["e" + (i + 1)]), 0), 100)
: 0;
return Inputs.range([0, 100], {
label: `Experiment ${i + 1} (%)`,
step: 1,
value
});
});

return Inputs.form(inputs);
}
Insert cell
---
Insert cell
html`${experimentCount ? ` Based on ${experimentCount} experiment${experimentCount > 1 ? 's' : ''}, the probability of success is <span style="
display: inline-block;
padding: 0.3em 0.5em;
font-size: 1.2em;
line-height: 1;
font-weight: bold;
font-family: sans-serif;
border-radius: 1000px;
background-color: ${getColor(probability)};
color: white;
">${ toPercentageString(probability) }</span>` : `👆 Enter results from your experiments<span style="
display: inline-block;
padding: 0.3em 0.5em;
font-size: 1.2em;
line-height: 1;
font-weight: bold;
font-family: sans-serif;
border-radius: 1000px;
opacity: 0;
">0%</span>`}`
Insert cell
viewof resultsView = Inputs.radio(["Simplified results", "Detailed results"], { value: "Simplified results", disabled: !experimentCount })
Insert cell
resultsView === 'Detailed results' ? getResultsTable() : getSimplifiedResultsTable()
Insert cell
---
Insert cell
## Background
Insert cell
This notebook is based on a formula and video series by [Alberto Savoia](https://www.albertosavoia.com/resources.html), who is a product innovation consult and speaker.
Insert cell
youtube("FVsLt9-m-zA")
Insert cell
Alberto has a YouTube video series called [Math of Success](https://www.youtube.com/playlist?list=PLStagpRSnu69I60CxqiWcDHccEVVhuyyo) and a book called [The Right It](https://www.youtube.com/watch?v=4sZMHAMN0DQ) where he presents the XYZ hypothesis and the math behind it. The formula is also available as an Excel version on his web site ([direct link](https://www.albertosavoia.com/uploads/1/4/0/9/14099067/simple_bayes_formula_for_product_manager.xlsx)).

Get free book on [pretotyping.org](https://www.pretotyping.org/resources.html).

[Thanks for the shout-out, Alberto](https://www.linkedin.com/posts/albertosavoia_xyz-hypothesis-activity-7031663726135058432-yh9x?utm_source=share&utm_medium=member_desktop).
Insert cell
---
Insert cell
### Source
Insert cell
probability = _.last(results) || 0
Insert cell
results = _.filter(_.map(experiments, 'verdict'))
Insert cell
colors = results.map(getColor)
Insert cell
experiments = {
const data = [];
const raw = _.values(dataInputs).map(p => p / 100);

for (let i = 0; i < raw.length; ++i) {
const result = raw[i];
const previousResult = i ? data[i - 1].weightedResult : 0;
const relativeResult = result / x;

// Reliability
let reliability = 0;
if (i) {
reliability = 0.1;
if (relativeResult >= 2) {
reliability = 0.9;
} else if (relativeResult >= 1) {
reliability = 0.7;
} else if (relativeResult >= 0.5) {
reliability = 0.3;
}
}

// Weighted result
// For first experiment this is always set at 0.1
let weightedResult = 0;
if (result) {
weightedResult = 0.1;

if (i) {
weightedResult = result
? (previousResult * reliability) /
(previousResult * reliability +
(1 - previousResult) * (1 - reliability))
: 0;
}
}

// Final verdict
let verdict = 0;
if (weightedResult >= 0.8) {
verdict = 0.9;
} else if (weightedResult >= 0.6) {
verdict = 0.7;
} else if (weightedResult >= 0.4) {
verdict = 0.5;
} else if (weightedResult >= 0.2) {
verdict = 0.3;
} else if (weightedResult) {
verdict = 0.1;
}

data.push({
result,
relativeResult,
reliability,
weightedResult,
verdict
});
}

return data;
}
Insert cell
---
Insert cell
Math behind this notebook. You don't have to worry about these.
Insert cell
maxExperimentCount = 5
Insert cell
x = xPercent / 100
Insert cell
experimentCount = results.length
Insert cell
colorValues = [
'#ff205b',
'#f1612d',
'#de7e15',
'#a9ad31',
'#62d198'
]
Insert cell
---
Insert cell
getColor = (val, max) => {
const maxVal = max || 1;
const r = val / maxVal;

if (r < 0.3) {
return colorValues[0]
}

if (r < 0.5) {
return colorValues[1]
}

if (r < 0.7) {
return colorValues[2]
}

if (r < 0.9) {
return colorValues[3]
}

return colorValues[4]
}
Insert cell
getResultsTable = () => {
return Inputs.table(
experiments.map((e, i) => {
const data = { ...e }
return {
name: 'Experiment ' + (i + 1),
..._.omit(data, ['reliability'])
};
}),
{
format: {
result: toPercentageString,
relativeResult: toPercentageString,
reliability: toPercentageString,
weightedResult: toPercentageString,
verdict (v) {
if (v) {
return html`${toPercentageString(v)} <span style="
display: inline-block;
border-radius: 100px;
height: 8px;
width: 8px;
background-color: ${getColor(v)};
" />`
}
return '–'
}
}
}
)
}
Insert cell
getSimplifiedResultsTable = () => {

return html`
<div style="display: flex; text-align: center;">
${experiments.map((e, i) => {
const n = i + 1;
const ord = n === 1 ? "st" : n === 2 ? "nd" : n === 3 ? "rd" : "th";

const percentage = Math.round(e.verdict * 100);
const color = e.verdict ? getColor(e.verdict) : "#fafafa";

return `
<div style="width: 6em; text-align: center;">
<div style="margin-left: auto; margin-right: auto; text-align: center; border-radius: 1000px; width: 2.6em; height: 2.6em; line-height: 2.6em; font-size: 1em; font-family: sans-serif; font-weight: bold; background-color: ${color}; color: ${
e.verdict ? "#fff" : "#bbb"
};">
${
percentage
? `${percentage}<span style="font-size: 0.6em;">%</span>`
: `–`
}
</div>
<div style="margin-top: 0.8em; font-size: 0.85em; line-height: 1.1;">
<div>After ${n}<span style="font-size: 0.7em;">${ord}</span></div>
<div style="font-size: 0.7em;">experiment</div>
</div>
</div>
`;
})}
</div>
`;
}
Insert cell
---
Insert cell
url = makeUrl(liveUrlParams)
Insert cell
liveUrlParams = {
const experimentParams = {}

for (let i = 0; i < experimentCount; ++i) {
if (experiments[i].result) {
experimentParams['e' + (i + 1)] = experiments[i].result * 100
}
}
return {
x: x * 100,
y,
z,
...experimentParams
}
}
Insert cell
---
Insert cell
toPercentageString = (float) => {
return Math.round(float * 100) + "%";
}
Insert cell
import { urlParams, makeUrl } from "@jerryjappinen/url-params"
Insert cell
import { youtube } from "@jerryjappinen/embed"
Insert cell
---
Insert cell
import { baseStyles } from "@jerryjappinen/ui"
Insert cell
baseStyles
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more