Merge pull request #499 from quantified-uncertainty/component-stats

Summary tables
This commit is contained in:
Ozzie Gooen 2022-05-09 16:36:48 -04:00 committed by GitHub
commit b07c6e6e9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 144 additions and 19 deletions

View File

@ -1,7 +1,11 @@
import * as React from "react";
import _ from "lodash";
import type { Distribution } from "@quri/squiggle-lang";
import { distributionErrorToString } from "@quri/squiggle-lang";
import {
Distribution,
result,
distributionError,
distributionErrorToString,
} from "@quri/squiggle-lang";
import { Vega, VisualizationSpec } from "react-vega";
import * as chartSpecification from "../vega-specs/spec-distributions.json";
import { ErrorBox } from "./ErrorBox";
@ -13,11 +17,14 @@ import {
expYScale,
} from "./DistributionVegaScales";
import styled from "styled-components";
import { NumberShower } from "./NumberShower";
type DistributionChartProps = {
distribution: Distribution;
width?: number;
height: number;
/** Whether to show a summary of means, stdev, percentiles etc */
showSummary: boolean;
/** Whether to show the user graph controls (scale etc) */
showControls?: boolean;
};
@ -25,6 +32,7 @@ type DistributionChartProps = {
export const DistributionChart: React.FC<DistributionChartProps> = ({
distribution,
height,
showSummary,
width,
showControls = false,
}: DistributionChartProps) => {
@ -37,7 +45,7 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
shape.value.continuous.some((x) => x.x <= 0) ||
shape.value.discrete.some((x) => x.x <= 0);
let spec = buildVegaSpec(isLogX, isExpY);
let widthProp = width ? width - 20 : size.width - 10;
let widthProp = width ? width : size.width;
// Check whether we should disable the checkbox
var logCheckbox = (
@ -58,21 +66,22 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
}
var result = (
<div>
<ChartContainer width={widthProp + "px"}>
<Vega
spec={spec}
data={{ con: shape.value.continuous, dis: shape.value.discrete }}
width={widthProp}
width={widthProp - 10}
height={height}
actions={false}
/>
{showSummary && <SummaryTable distribution={distribution} />}
{showControls && (
<div>
{logCheckbox}
<CheckBox label="Exp Y scale" value={isExpY} onChange={setExpY} />
</div>
)}
</div>
</ChartContainer>
);
} else {
var result = (
@ -87,6 +96,12 @@ export const DistributionChart: React.FC<DistributionChartProps> = ({
return sized;
};
type ChartContainerProps = { width: string };
let ChartContainer = styled.div<ChartContainerProps>`
width: ${(props) => props.width};
`;
function buildVegaSpec(isLogX: boolean, isExpY: boolean): VisualizationSpec {
return {
...chartSpecification,
@ -128,3 +143,90 @@ export const CheckBox = ({
</span>
);
};
type SummaryTableProps = {
distribution: Distribution;
};
const Table = styled.table`
margin-left: auto;
margin-right: auto;
border-collapse: collapse;
text-align: center;
border-style: hidden;
`;
const TableHead = styled.thead`
border-bottom: 1px solid rgb(141 149 167);
`;
const TableHeadCell = styled.th`
border-right: 1px solid rgb(141 149 167);
border-left: 1px solid rgb(141 149 167);
padding: 0.3em;
`;
const TableBody = styled.tbody``;
const Row = styled.tr``;
const Cell = styled.td`
padding: 0.3em;
border-right: 1px solid rgb(141 149 167);
border-left: 1px solid rgb(141 149 167);
`;
const SummaryTable: React.FC<SummaryTableProps> = ({
distribution,
}: SummaryTableProps) => {
let mean = distribution.mean();
let p5 = distribution.inv(0.05);
let p10 = distribution.inv(0.1);
let p25 = distribution.inv(0.25);
let p50 = distribution.inv(0.5);
let p75 = distribution.inv(0.75);
let p90 = distribution.inv(0.9);
let p95 = distribution.inv(0.95);
let unwrapResult = (
x: result<number, distributionError>
): React.ReactNode => {
if (x.tag === "Ok") {
return <NumberShower number={x.value} />;
} else {
return (
<ErrorBox heading="Distribution Error">
{distributionErrorToString(x.value)}
</ErrorBox>
);
}
};
return (
<Table>
<TableHead>
<Row>
<TableHeadCell>{"Mean"}</TableHeadCell>
<TableHeadCell>{"5%"}</TableHeadCell>
<TableHeadCell>{"10%"}</TableHeadCell>
<TableHeadCell>{"25%"}</TableHeadCell>
<TableHeadCell>{"50%"}</TableHeadCell>
<TableHeadCell>{"75%"}</TableHeadCell>
<TableHeadCell>{"90%"}</TableHeadCell>
<TableHeadCell>{"95%"}</TableHeadCell>
</Row>
</TableHead>
<TableBody>
<Row>
<Cell>{unwrapResult(mean)}</Cell>
<Cell>{unwrapResult(p5)}</Cell>
<Cell>{unwrapResult(p10)}</Cell>
<Cell>{unwrapResult(p25)}</Cell>
<Cell>{unwrapResult(p50)}</Cell>
<Cell>{unwrapResult(p75)}</Cell>
<Cell>{unwrapResult(p90)}</Cell>
<Cell>{unwrapResult(p95)}</Cell>
</Row>
</TableBody>
</Table>
);
};

View File

@ -61,6 +61,7 @@ export const FunctionChart: React.FC<{
distribution={mouseItem.value}
width={400}
height={140}
showSummary={false}
/>
) : (
<></>

View File

@ -65,6 +65,8 @@ export interface SquiggleItemProps {
expression: squiggleExpression;
width?: number;
height: number;
/** Whether to show a summary of statistics for distributions */
showSummary: boolean;
/** Whether to show type information */
showTypes?: boolean;
/** Whether to show users graph controls (scale etc) */
@ -75,6 +77,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
expression,
width,
height,
showSummary,
showTypes = false,
showControls = false,
}: SquiggleItemProps) => {
@ -103,6 +106,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
distribution={expression.value}
height={height}
width={width}
showSummary={showSummary}
showControls={showControls}
/>
</VariableBox>
@ -143,6 +147,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
height={50}
showTypes={showTypes}
showControls={showControls}
showSummary={showSummary}
/>
))}
</VariableBox>
@ -158,6 +163,7 @@ const SquiggleItem: React.FC<SquiggleItemProps> = ({
width={width !== undefined ? width - 20 : width}
height={50}
showTypes={showTypes}
showSummary={showSummary}
showControls={showControls}
/>
</>
@ -203,6 +209,8 @@ export interface SquiggleChartProps {
bindings?: bindings;
/** JS imported parameters */
jsImports?: jsImports;
/** Whether to show a summary of the distirbution */
showSummary?: boolean;
/** Whether to show type information about returns, default false */
showTypes?: boolean;
/** Whether to show graph controls (scale etc)*/
@ -223,6 +231,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
height = 60,
bindings = defaultBindings,
jsImports = defaultImports,
showSummary = false,
width,
showTypes = false,
showControls = false,
@ -246,6 +255,7 @@ export const SquiggleChart: React.FC<SquiggleChartProps> = ({
expression={expression}
width={width}
height={height}
showSummary={showSummary}
showTypes={showTypes}
showControls={showControls}
/>

View File

@ -43,7 +43,9 @@ export interface SquiggleEditorProps {
/** Whether to show detail about types of the returns, default false */
showTypes?: boolean;
/** Whether to give users access to graph controls */
showControls: boolean;
showControls?: boolean;
/** Whether to show a summary table */
showSummary?: boolean;
}
const Input = styled.div`
@ -67,6 +69,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
jsImports = defaultImports,
showTypes = false,
showControls = false,
showSummary = false,
}: SquiggleEditorProps) => {
let [expression, setExpression] = React.useState(initialSquiggleString);
return (
@ -95,6 +98,7 @@ export let SquiggleEditor: React.FC<SquiggleEditorProps> = ({
jsImports={jsImports}
showTypes={showTypes}
showControls={showControls}
showSummary={showSummary}
/>
</div>
);

View File

@ -40,18 +40,11 @@ function FieldFloat(Props: FieldFloatProps) {
);
}
interface Props {
initialSquiggleString?: string;
height?: number;
showTypes?: boolean;
showControls?: boolean;
}
interface Props2 {
interface ShowBoxProps {
height: number;
}
const ShowBox = styled.div<Props2>`
const ShowBox = styled.div<ShowBoxProps>`
border: 1px solid #eee;
border-radius: 2px;
height: ${(props) => props.height};
@ -76,12 +69,26 @@ const Row = styled.div`
`;
const Col = styled.div``;
let SquigglePlayground: FC<Props> = ({
interface PlaygroundProps {
/** The initial squiggle string to put in the playground */
initialSquiggleString?: string;
/** How many pixels high is the playground */
height?: number;
/** Whether to show the types of outputs in the playground */
showTypes?: boolean;
/** Whether to show the log scale controls in the playground */
showControls?: boolean;
/** Whether to show the summary table in the playground */
showSummary?: boolean;
}
let SquigglePlayground: FC<PlaygroundProps> = ({
initialSquiggleString = "",
height = 300,
showTypes = false,
showControls = false,
}: Props) => {
showSummary = false,
}: PlaygroundProps) => {
let [squiggleString, setSquiggleString] = useState(initialSquiggleString);
let [sampleCount, setSampleCount] = useState(1000);
let [outputXYPoints, setOutputXYPoints] = useState(1000);
@ -114,6 +121,7 @@ let SquigglePlayground: FC<Props> = ({
height={150}
showTypes={showTypes}
showControls={showControls}
showSummary={showSummary}
/>
</Display>
</Col>
@ -122,7 +130,7 @@ let SquigglePlayground: FC<Props> = ({
);
};
export default SquigglePlayground;
export function renderSquigglePlaygroundToDom(props: Props) {
export function renderSquigglePlaygroundToDom(props: PlaygroundProps) {
let parent = document.createElement("div");
ReactDOM.render(<SquigglePlayground {...props} />, parent);
return parent;