1
+ // https://observablehq.com/@d 3/kernel-density-estimation@109
2
+ export default function define ( runtime , observer ) {
3
+ const main = runtime . module ( ) ;
4
+ main . variable ( observer ( ) ) . define ( [ "md" ] , function ( md ) { return (
5
+ md `# Kernel Density Estimation
6
+
7
+ [Kernel density estimation](https://en.wikipedia.org/wiki/Kernel_density_estimation), or KDE, estimates the probability distribution of a random variable. The kernel’s *bandwidth* determines the estimate’s smoothness: if the bandwidth is too small, the estimate may include spurious bumps and wiggles; too large, and the estimate reveals little about the underlying distribution.
8
+
9
+ This example, based on [work by John Firebaugh](https://bl.ocks.org/jfirebaugh/900762), shows times between eruptions of [Old Faithful](https://en.wikipedia.org/wiki/Old_Faithful). See also a [two-dimensional density estimation](/@d3/density-contours) of this data.`
10
+ ) } ) ;
11
+ main . variable ( observer ( "viewof bandwidth" ) ) . define ( "viewof bandwidth" , [ "html" ] , function ( html )
12
+ {
13
+ const form = html `< form >
14
+ < input name =i type =range min =1 max =20 value =7 step =any style ="width:180px; ">
15
+ < output style ="font-size:smaller;font-style:oblique; " name =o > </ output >
16
+ </ form > `;
17
+ form . i . oninput = ( ) => form . o . value = `${ ( form . value = form . i . valueAsNumber ) . toFixed ( 1 ) } bandwidth` ;
18
+ form . i . oninput ( ) ;
19
+ return form ;
20
+ }
21
+ ) ;
22
+ main . variable ( observer ( "bandwidth" ) ) . define ( "bandwidth" , [ "Generators" , "viewof bandwidth" ] , ( G , _ ) => G . input ( _ ) ) ;
23
+ main . variable ( observer ( "chart" ) ) . define ( "chart" , [ "d3" , "width" , "height" , "bins" , "x" , "y" , "data" , "density" , "line" , "xAxis" , "yAxis" ] , function ( d3 , width , height , bins , x , y , data , density , line , xAxis , yAxis )
24
+ {
25
+ const svg = d3 . create ( "svg" )
26
+ . attr ( "viewBox" , [ 0 , 0 , width , height ] ) ;
27
+
28
+ svg . append ( "g" )
29
+ . attr ( "fill" , "#bbb" )
30
+ . selectAll ( "rect" )
31
+ . data ( bins )
32
+ . join ( "rect" )
33
+ . attr ( "x" , d => x ( d . x0 ) + 1 )
34
+ . attr ( "y" , d => y ( d . length / data . length ) )
35
+ . attr ( "width" , d => x ( d . x1 ) - x ( d . x0 ) - 1 )
36
+ . attr ( "height" , d => y ( 0 ) - y ( d . length / data . length ) ) ;
37
+
38
+ svg . append ( "path" )
39
+ . datum ( density )
40
+ . attr ( "fill" , "none" )
41
+ . attr ( "stroke" , "#000" )
42
+ . attr ( "stroke-width" , 1.5 )
43
+ . attr ( "stroke-linejoin" , "round" )
44
+ . attr ( "d" , line ) ;
45
+
46
+ svg . append ( "g" )
47
+ . call ( xAxis ) ;
48
+
49
+ svg . append ( "g" )
50
+ . call ( yAxis ) ;
51
+
52
+ return svg . node ( ) ;
53
+ }
54
+ ) ;
55
+ main . variable ( observer ( "kde" ) ) . define ( "kde" , [ "d3" ] , function ( d3 ) { return (
56
+ function kde ( kernel , thresholds , data ) {
57
+ return thresholds . map ( t => [ t , d3 . mean ( data , d => kernel ( t - d ) ) ] ) ;
58
+ }
59
+ ) } ) ;
60
+ main . variable ( observer ( "epanechnikov" ) ) . define ( "epanechnikov" , function ( ) { return (
61
+ function epanechnikov ( bandwidth ) {
62
+ return x => Math . abs ( x /= bandwidth ) <= 1 ? 0.75 * ( 1 - x * x ) / bandwidth : 0 ;
63
+ }
64
+ ) } ) ;
65
+ main . variable ( observer ( "line" ) ) . define ( "line" , [ "d3" , "x" , "y" ] , function ( d3 , x , y ) { return (
66
+ d3 . line ( )
67
+ . curve ( d3 . curveBasis )
68
+ . x ( d => x ( d [ 0 ] ) )
69
+ . y ( d => y ( d [ 1 ] ) )
70
+ ) } ) ;
71
+ main . variable ( observer ( "x" ) ) . define ( "x" , [ "d3" , "data" , "margin" , "width" ] , function ( d3 , data , margin , width ) { return (
72
+ d3 . scaleLinear ( )
73
+ . domain ( d3 . extent ( data ) ) . nice ( )
74
+ . range ( [ margin . left , width - margin . right ] )
75
+ ) } ) ;
76
+ main . variable ( observer ( "y" ) ) . define ( "y" , [ "d3" , "bins" , "data" , "height" , "margin" ] , function ( d3 , bins , data , height , margin ) { return (
77
+ d3 . scaleLinear ( )
78
+ . domain ( [ 0 , d3 . max ( bins , d => d . length ) / data . length ] )
79
+ . range ( [ height - margin . bottom , margin . top ] )
80
+ ) } ) ;
81
+ main . variable ( observer ( "xAxis" ) ) . define ( "xAxis" , [ "height" , "margin" , "d3" , "x" , "width" , "data" ] , function ( height , margin , d3 , x , width , data ) { return (
82
+ g => g
83
+ . attr ( "transform" , `translate(0,${ height - margin . bottom } )` )
84
+ . call ( d3 . axisBottom ( x ) )
85
+ . call ( g => g . append ( "text" )
86
+ . attr ( "x" , width - margin . right )
87
+ . attr ( "y" , - 6 )
88
+ . attr ( "fill" , "#000" )
89
+ . attr ( "text-anchor" , "end" )
90
+ . attr ( "font-weight" , "bold" )
91
+ . text ( data . title ) )
92
+ ) } ) ;
93
+ main . variable ( observer ( "yAxis" ) ) . define ( "yAxis" , [ "margin" , "d3" , "y" ] , function ( margin , d3 , y ) { return (
94
+ g => g
95
+ . attr ( "transform" , `translate(${ margin . left } ,0)` )
96
+ . call ( d3 . axisLeft ( y ) . ticks ( null , "%" ) )
97
+ . call ( g => g . select ( ".domain" ) . remove ( ) )
98
+ ) } ) ;
99
+ main . variable ( observer ( "thresholds" ) ) . define ( "thresholds" , [ "x" ] , function ( x ) { return (
100
+ x . ticks ( 40 )
101
+ ) } ) ;
102
+ main . variable ( observer ( "density" ) ) . define ( "density" , [ "kde" , "epanechnikov" , "bandwidth" , "thresholds" , "data" ] , function ( kde , epanechnikov , bandwidth , thresholds , data ) { return (
103
+ kde ( epanechnikov ( bandwidth ) , thresholds , data )
104
+ ) } ) ;
105
+ main . variable ( observer ( "bins" ) ) . define ( "bins" , [ "d3" , "x" , "thresholds" , "data" ] , function ( d3 , x , thresholds , data ) { return (
106
+ d3 . histogram ( )
107
+ . domain ( x . domain ( ) )
108
+ . thresholds ( thresholds )
109
+ ( data )
110
+ ) } ) ;
111
+ main . variable ( observer ( "data" ) ) . define ( "data" , [ "d3" ] , async function ( d3 ) { return (
112
+ Object . assign ( await d3 . json ( "https://gist.githubusercontent.com/mbostock/4341954/raw/99757f8851178fbf60ff2173f322d1eb702d250f/faithful.json" ) , { title : "Time between eruptions (min.)" } )
113
+ ) } ) ;
114
+ main . variable ( observer ( "height" ) ) . define ( "height" , function ( ) { return (
115
+ 500
116
+ ) } ) ;
117
+ main . variable ( observer ( "margin" ) ) . define ( "margin" , function ( ) { return (
118
+ { top : 20 , right : 30 , bottom : 30 , left : 40 }
119
+ ) } ) ;
120
+ main . variable ( observer ( "d3" ) ) . define ( "d3" , [ "require" ] , function ( require ) { return (
121
+ require ( "d3@5" )
122
+ ) } ) ;
123
+ return main ;
124
+ }
0 commit comments