bibframe-hub-collocation / hub-collocation.html
jimfhahn's picture
Deploy hub-collocation.html 0f3f53f
35026d2 verified
Raw History Blame Contribute Delete
36.4 kB
<!doctype html><html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style type="text/css">:root{--serif:"Source Serif 4 Variable", ui-serif, serif;--sans-serif:"Inter Variable", ui-sans-serif, sans-serif;--monospace:"Spline Sans Mono Variable", Menlo, Consolas, monospace;--monospace-font:14px/1.5 var(--monospace);--max-width:calc(1024px - 4 * 16px)}html{font:17px/1.5 var(--serif);-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background:var(--theme-background);color:var(--theme-foreground);margin:16px 0;padding:16px 24px}main{max-width:var(--max-width);margin:auto}h1,h2,h3,h4,h5,h6{color:var(--theme-foreground-alt);text-wrap:balance;margin-top:0;margin-bottom:.25rem;font-weight:700;line-height:1.15}h2+p,h3+p,h4+p,h2+table,h3+table,h4+table{margin-top:0}.observablehq:first-of-type h1+h2{color:var(--theme-foreground);margin-bottom:1rem;font-size:20px;font-style:italic;font-weight:400}a[href]{color:var(--theme-foreground-focus);text-decoration:none}a[href]:hover,a[href]:focus{text-decoration:underline}pre,code,tt{font-family:var(--monospace);font-size:82.3529%}pre code,figcaption code{font-size:inherit}pre[data-language]{position:relative}pre[data-language]:before{content:attr(data-language);text-align:right;pointer-events:none;height:0;color:var(--theme-foreground-muted);margin-right:-4px;font-size:12px;line-height:21px;display:block;position:sticky;top:0;left:0}h1 code,h2 code,h3 code,h4 code,h5 code,h6 code{font-size:88.2353%}h2[id] a[href],h3[id] a[href]{color:inherit}pre{line-height:1.5}img{max-width:100%}p,table,figure,figcaption,h1,h2,h3,h4,h5,h6,.katex-display{max-width:640px}blockquote,ol,ul{max-width:600px}blockquote{margin:1rem 1.5rem}ul,ol{padding-left:28px}hr{background:no-repeat center/100% 1px linear-gradient(to right, var(--theme-foreground-faintest), var(--theme-foreground-faintest));border:none;height:1px;margin:1rem 0;padding:1rem 0}pre{background-color:var(--theme-background-alt);overscroll-behavior-x:none;box-sizing:border-box;border-radius:4px;min-height:1.5em;margin:1rem -1rem;padding:4px 1rem;overflow-x:auto}pre>code:only-child{width:fit-content;display:block}input:not([type]),input[type=email],input[type=number],input[type=password],input[type=range],input[type=search],input[type=tel],input[type=text],input[type=url]{width:240px}input,canvas,button{vertical-align:middle}button,input,textarea{accent-color:var(--theme-foreground-focus)}table{border-collapse:collapse;width:100%;font:13px/1.2 var(--sans-serif)}table pre,table code,table tt{font-size:inherit;line-height:inherit}th>pre:only-child,td>pre:only-child{margin:0;padding:0}th{color:var(--theme-foreground);text-align:left;vertical-align:bottom}td{color:var(--theme-foreground-alt);vertical-align:top}th,td{padding:3px 6.5px 3px 0}th:last-child,td:last-child{padding-right:0}tr:not(:last-child){border-bottom:solid 1px var(--theme-foreground-faintest)}thead tr{border-bottom:solid 1px var(--theme-foreground-fainter)}figure,table{margin:1rem 0}figure img{max-width:100%}figure>h2,figure>h3{font-family:var(--sans-serif)}figure>h2{font-size:20px}figure>h3{font-size:16px;font-weight:400}figcaption{font:small var(--sans-serif);color:var(--theme-foreground-muted)}.observablehq--collapsed,.observablehq--expanded.observablehq--inspect a{cursor:pointer}.observablehq--caret{vertical-align:baseline;margin-right:4px}.observablehq--field{text-indent:-1rem;margin-left:1rem}.observablehq--inspect{font:var(--monospace-font);white-space:pre;padding:2px 0;display:block;overflow-x:auto}.observablehq--inspect.observablehq--import{white-space:normal}.observablehq--inspect::-webkit-scrollbar{display:none}.observablehq--error .observablehq--inspect{word-break:break-all;white-space:pre-wrap}.observablehq--string-expand{background:var(--theme-background-alt);cursor:pointer;vertical-align:middle;border-radius:2px;margin-left:6px;padding:2px 6px;font-size:80%}.observablehq--keyword{color:var(--syntax-keyword)}.observablehq--index,.observablehq--key{color:var(--syntax-definition)}.observablehq--symbol,.observablehq--regexp,.observablehq--string{color:var(--syntax-string)}.observablehq--undefined,.observablehq--null{color:var(--syntax-keyword)}.observablehq--prototype-key{color:var(--syntax-comment)}.observablehq--bigint,.observablehq--boolean,.observablehq--date,.observablehq--forbidden,.observablehq--number{color:var(--syntax-literal)}.observablehq--error{color:var(--theme-error)}.observablehq--cell{min-height:1px;margin:17px 0;position:relative}.tok-link{color:var(--syntax-link)}.tok-strong{font-weight:600}.tok-emphasis{font-style:italic}.tok-keyword{color:var(--syntax-keyword)}.tok-atom{color:var(--syntax-atom)}.tok-literal{color:var(--syntax-literal)}.tok-string{color:var(--syntax-string)}.tok-comment{color:var(--syntax-comment)}.tok-invalid{color:var(--syntax-invalid)}.tok-variable{color:var(--syntax-variable)}.tok-definition{color:var(--syntax-definition)}.tok-meta{color:var(--syntax-meta)}.plot-d6a7b5{--plot-background:var(--theme-background)}p .plot-d6a7b5{display:inline-block}.observablehq--cell:empty{min-height:0}.observablehq--cell:not(:empty)+pre{margin-top:-11px}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(./assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(./assets/inter-cyrillic-wght-normal-DqGufNeO.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(./assets/inter-greek-ext-wght-normal-DlzME5K_.woff2)format("woff2-variations");unicode-range:U+1F??}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(./assets/inter-greek-wght-normal-CkhJZR-_.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(./assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(./assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(./assets/inter-latin-wght-normal-Dx4kXJAl.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter Variable;font-style:italic;font-display:swap;font-weight:100 900;src:url(./assets/inter-cyrillic-ext-wght-italic-B5xAaiFk.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter Variable;font-style:italic;font-display:swap;font-weight:100 900;src:url(./assets/inter-cyrillic-wght-italic-DzZdc28x.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Inter Variable;font-style:italic;font-display:swap;font-weight:100 900;src:url(./assets/inter-greek-ext-wght-italic-DcOpz6Lw.woff2)format("woff2-variations");unicode-range:U+1F??}@font-face{font-family:Inter Variable;font-style:italic;font-display:swap;font-weight:100 900;src:url(./assets/inter-greek-wght-italic-CILZdfAp.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Inter Variable;font-style:italic;font-display:swap;font-weight:100 900;src:url(./assets/inter-vietnamese-wght-italic-K3WlGtc8.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter Variable;font-style:italic;font-display:swap;font-weight:100 900;src:url(./assets/inter-latin-ext-wght-italic-0pjOp8NU.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter Variable;font-style:italic;font-display:swap;font-weight:100 900;src:url(./assets/inter-latin-wght-italic-DpCbqKDY.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Serif 4 Variable";font-style:normal;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-cyrillic-ext-wght-normal-cHuGMAiY.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Serif 4 Variable";font-style:normal;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-cyrillic-wght-normal-DwHjdM4S.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:"Source Serif 4 Variable";font-style:normal;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-greek-wght-normal-ece1310W.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:"Source Serif 4 Variable";font-style:normal;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-vietnamese-wght-normal-D-yuW59g.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Serif 4 Variable";font-style:normal;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-latin-ext-wght-normal-CxNHBx-q.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Serif 4 Variable";font-style:normal;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-latin-wght-normal-D9elroTD.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:"Source Serif 4 Variable";font-style:italic;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-cyrillic-ext-wght-italic-BXuSHrjR.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:"Source Serif 4 Variable";font-style:italic;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-cyrillic-wght-italic-RLIThRd4.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:"Source Serif 4 Variable";font-style:italic;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-greek-wght-italic-F2ziEGcb.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:"Source Serif 4 Variable";font-style:italic;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-vietnamese-wght-italic-C3q834ic.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:"Source Serif 4 Variable";font-style:italic;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-latin-ext-wght-italic-CpkvI5hy.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:"Source Serif 4 Variable";font-style:italic;font-display:swap;font-weight:200 900;src:url(./assets/source-serif-4-latin-wght-italic-D2yaqPoE.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Spline Sans Mono Variable;font-style:normal;font-display:swap;font-weight:300 700;src:url(./assets/spline-sans-mono-latin-ext-wght-normal-Dh0aNLWd.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Spline Sans Mono Variable;font-style:normal;font-display:swap;font-weight:300 700;src:url(./assets/spline-sans-mono-latin-wght-normal-DlaB5ohX.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Spline Sans Mono Variable;font-style:italic;font-display:swap;font-weight:300 700;src:url(./assets/spline-sans-mono-latin-ext-wght-italic-RJy045_w.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Spline Sans Mono Variable;font-style:italic;font-display:swap;font-weight:300 700;src:url(./assets/spline-sans-mono-latin-wght-italic-DPjO1keU.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}</style>
<meta name="generator" content="Observable Notebook Kit v2.6.4"><title>BIBFRAME Hubs collocate records across classification</title> <script type="module" crossorigin src="./assets/hub-collocation-CI5CJGX0.js"></script>
<link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-DK3Fl9T5.js">
<link rel="modulepreload" crossorigin href="./assets/preload-helper-uBIymjUX.js">
<link rel="modulepreload" crossorigin href="./assets/fileAttachment-e5diAeLP.js">
</head>
<body>
<main><div id="cell-1" class="observablehq observablehq--cell"><div><h1>Hubs collocate records across classification</h1>
<p>A library shelves each book once, by class. Oliver Sacks’s case studies <em>The man who mistook
his wife for a hat</em> sit in <strong>R</strong> (medicine); Michael Nyman’s chamber opera of the same name sits
in <strong>M</strong> (music), so classification splits them. LC subject headings don’t rejoin them either.
Subject headings group records by what they are <em>about</em>: the book has <em>Neurology — Anecdotes</em>,
the opera <em>Operas — Scores</em> and <em>Visual agnosia — Drama</em>. The one existing link is inside the
opera’s records: an added entry for <em>Sacks, Oliver. Man who mistook his wife for a hat…</em>. It
points one way, appears only in the opera’s records, and doesn’t say how the two are related.</p>
<p>Library of Congress <strong>BIBFRAME Hubs</strong> turn that into a link between the works themselves. A Hub
is the authorized heading for a work (<em>Sacks, Oliver. Man who mistook his wife for a hat</em>) with
a URI, and Hubs carry typed relationships to each other: <em>opera adaptation of</em>, <em>based on</em>,
<em>libretto for</em>. Once bibliographic records are attached to their Hubs, every record of the book
is one typed hop from every record of the opera, whatever shelf or subject headings each has.
Section 2 checks how often subject headings or a record’s own added entries already connect
such pairs.</p>
<p>This page shows that, using <strong>437,836 LC bibliographic records matched to their Hub at high
confidence</strong> (about 96% exactly right in a hand check). The match is the <code>bib_to_hub</code> table in
<a href="https://huggingface.co/datasets/jimfhahn/lc-bibframe-hubs#hubs-collocate-records-across-classification">jimfhahn/lc-bibframe-hubs</a>;
the records come from LC’s experimental embeddings release
(<a href="https://huggingface.co/datasets/jimfhahn/lc-bib-embeddings">jimfhahn/lc-bib-embeddings</a>, 12.2M
records). Everything runs in your browser. To browse any Hub’s whole family, use the
<a href="https://jimfhahn-bibframe-hub-explorer.static.hf.space/">Hub family explorer</a>.</p>
<p>One gap to keep in mind: the source has <strong>no class P (language and literature) and no T
(technology)</strong> yet, because LC’s files for them are not downloadable. P is where most
adaptations start, so every count here is a floor.</p>
</div></div><div id="cell-2" class="observablehq observablehq--cell"></div><div id="cell-3" class="observablehq observablehq--cell"></div><pre data-language="js"><code class="language-js"><span class="tok-comment">// Four small Parquet files (~0.5 MB) written by tools/bib-hub-mapping/build_bib_to_hub.py</span>
<span class="tok-comment">// --collocation. Same loader as the Hub family explorer: a same-origin copy in ./data/</span>
<span class="tok-comment">// first (deploy-space.sh puts it there), then the dataset repo.</span>
<span class="tok-keyword">const</span> <span class="tok-definition">DATASET</span> = <span class="tok-string">"https://huggingface.co/datasets/jimfhahn/lc-bibframe-hubs/resolve/main/collocation/"</span>;
<span class="tok-keyword">const</span> <span class="tok-definition">SOURCES</span> = [<span class="tok-keyword">new</span> <span class="tok-definition">URL</span>(<span class="tok-string">"./data/"</span>, <span class="tok-variable">location</span>.<span class="tok-definition">href</span>).<span class="tok-definition">href</span>, <span class="tok-variable">DATASET</span>];
<span class="tok-keyword">const</span> <span class="tok-definition">db</span> = <span class="tok-keyword">await</span> <span class="tok-variable">DuckDBClient</span>.<span class="tok-definition">of</span>();
<span class="tok-keyword">const</span> <span class="tok-definition">isParquet</span> = (<span class="tok-definition">b</span>) =&gt; <span class="tok-variable">b</span>.<span class="tok-definition">length</span> &gt; <span class="tok-literal">8</span> &amp;&amp; <span class="tok-variable">b</span>[<span class="tok-literal">0</span>] === <span class="tok-literal">0x50</span> &amp;&amp; <span class="tok-variable">b</span>[<span class="tok-literal">1</span>] === <span class="tok-literal">0x41</span> &amp;&amp; <span class="tok-variable">b</span>[<span class="tok-literal">2</span>] === <span class="tok-literal">0x52</span> &amp;&amp; <span class="tok-variable">b</span>[<span class="tok-literal">3</span>] === <span class="tok-literal">0x31</span>; <span class="tok-comment">// "PAR1"</span>
<span class="tok-keyword">async</span> <span class="tok-keyword">function</span> <span class="tok-definition">attach</span>(<span class="tok-definition">name</span>) {
<span class="tok-keyword">const</span> <span class="tok-definition">errors</span> = [];
<span class="tok-keyword">for</span> (<span class="tok-keyword">const</span> <span class="tok-definition">base</span> <span class="tok-keyword">of</span> <span class="tok-variable">SOURCES</span>) {
<span class="tok-keyword">try</span> {
<span class="tok-keyword">const</span> <span class="tok-definition">res</span> = <span class="tok-keyword">await</span> <span class="tok-variable">fetch</span>(<span class="tok-variable">base</span> + <span class="tok-variable">name</span>);
<span class="tok-keyword">if</span> (!<span class="tok-variable">res</span>.<span class="tok-definition">ok</span>) <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> <span class="tok-definition">Error</span>(<span class="tok-string">`HTTP </span>${<span class="tok-variable">res</span>.<span class="tok-definition">status</span>}<span class="tok-string">`</span>);
<span class="tok-keyword">const</span> <span class="tok-definition">bytes</span> = <span class="tok-keyword">new</span> <span class="tok-definition">Uint8Array</span>(<span class="tok-keyword">await</span> <span class="tok-variable">res</span>.<span class="tok-definition">arrayBuffer</span>());
<span class="tok-keyword">if</span> (!<span class="tok-variable">isParquet</span>(<span class="tok-variable">bytes</span>)) <span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> <span class="tok-definition">Error</span>(<span class="tok-string">"not a Parquet file"</span>);
<span class="tok-keyword">return</span> <span class="tok-variable">db</span>.<span class="tok-definition">_db</span>.<span class="tok-definition">registerFileBuffer</span>(<span class="tok-variable">name</span>, <span class="tok-variable">bytes</span>);
} <span class="tok-keyword">catch</span> (<span class="tok-definition">e</span>) {
<span class="tok-variable">errors</span>.<span class="tok-definition">push</span>(<span class="tok-string">`</span>${<span class="tok-variable">base</span>}${<span class="tok-variable">name</span>}<span class="tok-string">: </span>${<span class="tok-variable">e</span>.<span class="tok-definition">message</span>}<span class="tok-string">`</span>);
}
}
<span class="tok-keyword">throw</span> <span class="tok-keyword">new</span> <span class="tok-definition">Error</span>(<span class="tok-string">`Could not load </span>${<span class="tok-variable">name</span>}<span class="tok-string">:</span><span class="tok-literal">\n</span>${<span class="tok-variable">errors</span>.<span class="tok-definition">join</span>(<span class="tok-string">"</span><span class="tok-literal">\n</span><span class="tok-string">"</span>)}<span class="tok-string">`</span>);
}
<span class="tok-keyword">const</span> <span class="tok-definition">FILES</span> = {<span class="tok-definition">hub_class</span>: <span class="tok-string">"colloc_hub_class.parquet"</span>, <span class="tok-definition">edges</span>: <span class="tok-string">"colloc_edges.parquet"</span>,
<span class="tok-definition">records</span>: <span class="tok-string">"colloc_records.parquet"</span>, <span class="tok-definition">coverage</span>: <span class="tok-string">"colloc_coverage.parquet"</span>};
<span class="tok-keyword">await</span> <span class="tok-variable">Promise</span>.<span class="tok-definition">all</span>(<span class="tok-variable">Object</span>.<span class="tok-definition">values</span>(<span class="tok-variable">FILES</span>).<span class="tok-definition">map</span>(<span class="tok-variable">attach</span>));
<span class="tok-keyword">for</span> (<span class="tok-keyword">const</span> [<span class="tok-definition">table</span>, <span class="tok-definition">file</span>] <span class="tok-keyword">of</span> <span class="tok-variable">Object</span>.<span class="tok-definition">entries</span>(<span class="tok-variable">FILES</span>)) {
<span class="tok-keyword">await</span> <span class="tok-variable">db</span>.<span class="tok-definition">query</span>(<span class="tok-string">`CREATE TABLE </span>${<span class="tok-variable">table</span>}<span class="tok-string"> AS SELECT * FROM read_parquet('</span>${<span class="tok-variable">file</span>}<span class="tok-string">')`</span>);
}</code></pre><div id="cell-4" class="observablehq observablehq--cell"></div><div id="cell-5" class="observablehq observablehq--cell"></div><div id="cell-6" class="observablehq observablehq--cell"><div><h2 id="-1-one-work-two-shelves" tabindex="-1"><a href="#-1-one-work-two-shelves">1. One work, two shelves</a></h2>
<p>Pick a pair of works joined by a creative relationship. Each side lists the LC records matched
to that work’s Hub, grouped by the class they are shelved in. Titles link to LC’s BIBFRAME
description of the record; the work names open the Hub in the family explorer.</p>
</div></div><div id="cell-7" class="observablehq observablehq--cell"></div><pre data-language="js"><code class="language-js"><span class="tok-keyword">const</span> <span class="tok-definition">SACKS</span> = <span class="tok-string">"a9402977-4b49-89c3-e703-6f4e0c969790"</span>;
<span class="tok-keyword">const</span> <span class="tok-definition">pair</span> = <span class="tok-variable">view</span>(<span class="tok-variable">Inputs</span>.<span class="tok-definition">select</span>(<span class="tok-variable">crossPairs</span>, {
<span class="tok-definition">label</span>: <span class="tok-string">"Pair"</span>,
<span class="tok-definition">value</span>: <span class="tok-variable">crossPairs</span>.<span class="tok-definition">find</span>((<span class="tok-definition">p</span>) =&gt; <span class="tok-variable">p</span>.<span class="tok-definition">a</span> === <span class="tok-variable">SACKS</span> || <span class="tok-variable">p</span>.<span class="tok-definition">b</span> === <span class="tok-variable">SACKS</span>),
<span class="tok-definition">format</span>: (<span class="tok-definition">p</span>) =&gt; <span class="tok-string">`</span>${<span class="tok-variable">trunc</span>(<span class="tok-variable">shortHub</span>(<span class="tok-variable">p</span>.<span class="tok-definition">from</span>), <span class="tok-literal">45</span>)}<span class="tok-string"> → </span>${<span class="tok-variable">p</span>.<span class="tok-definition">relText</span>}<span class="tok-string"> → </span>${<span class="tok-variable">trunc</span>(<span class="tok-variable">shortHub</span>(<span class="tok-variable">p</span>.<span class="tok-definition">to</span>), <span class="tok-literal">45</span>)}<span class="tok-string">`</span>
}));</code></pre><div id="cell-8" class="observablehq observablehq--cell"></div><div id="cell-9" class="observablehq observablehq--cell"><div><h2 id="-2-every-creative-link-that-crosses-shelves" tabindex="-1"><a href="#-2-every-creative-link-that-crosses-shelves">2. Every creative link that crosses shelves</a></h2>
<p>Each row is a pair of works joined by a creative relationship (adaptation, libretto, musical
setting, based on…) where both works have matched LC records and the records sit in different
classes. Dots show where each work’s records are shelved; the grey line is the distance the Hub
relationship bridges. Columns P and T are empty because the source has no records there; the
novels and plays that do appear sit in <strong>A</strong> because LC classes collected sets such as <em>Great
Books of the Western World</em> (<code>AC1 .G72</code>) and some translation series there.</p>
</div></div><div id="cell-10" class="observablehq observablehq--cell"></div><div id="cell-11" class="observablehq observablehq--cell"></div><div id="cell-21" class="observablehq observablehq--cell"><div><p><strong>Don’t subject headings already do this?</strong> Sometimes. We checked these 40 pairs against LC’s
MARC records (up to five per side, October 2026). The two sides share an identical LCSH heading
in only 3 pairs (<em>Faust, -approximately 1540 — Drama</em>; <em>Othello (Fictitious character from Shakespeare) — Drama</em>; <em>Operas — Librettos</em>). They share a main heading in 14, mostly a person or character both
works are about (Alexander Hamilton, Benvenuto Cellini, Athaliah, Mongkut) or a broad form
(<em>Operas</em>; <em>Hymns, English</em>). Subject headings collocate by topic, which complements Hubs, but
they rarely record that one work adapts another.</p>
<p>The closer equivalent is in the records themselves. In 28 pairs, a record on one side names the
other work in a name/title entry (600/700/730), sometimes with a relationship designator
(Ellington’s <em>Beautiful Indians</em>: <em>Inspired by (work): Longfellow, Henry Wadsworth. Song of
Hiawatha</em>). That link is one-way and attached to that record only. The Hub relationship joins
every record on both sides. In the other 12 pairs no record names the other work, so the Hub
is the only link between them, apart from four that share a main heading (<em>Cellini, Benvenuto</em> twice; <em>Operas</em>;
<em>Hymns, English</em>). Most are 19th-century stage works: Scribe and Boieldieu’s <em>La dame blanche</em> after
Scott’s <em>Monastery</em> and <em>Abbot</em>, Gevaert’s <em>Quentin Durward</em>, Bishop’s <em>Heart of Mid-Lothian</em>,
Solomon’s <em>Pickwick</em>, Lassen’s <em>Faust</em> music. Script:
<a href="https://github.com/jimfhahn/vufind-bf-hubs-plugin/tree/main/tools/bib-hub-mapping"><code>tools/bib-hub-mapping/lcsh_overlap.py</code></a>.</p>
</div></div><div id="cell-12" class="observablehq observablehq--cell"><div><h2 id="-3-one-work-many-shelves" tabindex="-1"><a href="#-3-one-work-many-shelves">3. One work, many shelves</a></h2>
<p>Collocation also happens on a single Hub: one work whose editions LC classed under different
subjects. The U.S. Constitution sits in general works, American history, political science and
law; Machiavelli’s <em>Prince</em>, Franklin’s <em>Autobiography</em> and Hobbes’s <em>Leviathan</em> each span three
classes. Collective titles (<em>Works. Selections</em>, <em>Essays</em>) gather different books by one author
rather than one work, so they are hidden by default. Many of these Hubs (<em>Prince</em>,
<em>Autobiography</em>) have no links to other Hubs, so their table link goes to <code>id.loc.gov</code>
rather than the family explorer.</p>
</div></div><div id="cell-13" class="observablehq observablehq--cell"></div><pre data-language="js"><code class="language-js"><span class="tok-keyword">const</span> <span class="tok-definition">hideCollective</span> = <span class="tok-variable">view</span>(<span class="tok-variable">Inputs</span>.<span class="tok-definition">toggle</span>({<span class="tok-definition">label</span>: <span class="tok-string">"Hide collective titles"</span>, <span class="tok-definition">value</span>: <span class="tok-literal">true</span>}));</code></pre><div id="cell-14" class="observablehq observablehq--cell"></div><pre data-language="js"><code class="language-js"><span class="tok-keyword">const</span> <span class="tok-definition">topN</span> = <span class="tok-variable">view</span>(<span class="tok-variable">Inputs</span>.<span class="tok-definition">range</span>([<span class="tok-literal">10</span>, <span class="tok-literal">60</span>], {<span class="tok-definition">label</span>: <span class="tok-string">"Works shown"</span>, <span class="tok-definition">step</span>: <span class="tok-literal">5</span>, <span class="tok-definition">value</span>: <span class="tok-literal">25</span>}));</code></pre><div id="cell-15" class="observablehq observablehq--cell"></div><div id="cell-16" class="observablehq observablehq--cell"></div><div id="cell-17" class="observablehq observablehq--cell"><div><h2 id="-4-how-much-of-the-catalogue-this-reaches" tabindex="-1"><a href="#-4-how-much-of-the-catalogue-this-reaches">4. How much of the catalogue this reaches</a></h2>
<p>Share of each class’s records matched to a Hub. Music leads because composers’ works are
almost always under uniform titles; most other classes sit at 2–5%. A record can only be
matched where LC made a name/title authority for its work.</p>
</div></div><div id="cell-18" class="observablehq observablehq--cell"></div><div id="cell-19" class="observablehq observablehq--cell"><div><h2 id="-5-how-far-to-trust-it" tabindex="-1"><a href="#-5-how-far-to-trust-it">5. How far to trust it</a></h2>
<p>Records were matched offline: each record’s first creator and title (normalized, subtitle
dropped) against each Hub’s authorized access point, then against the Hub’s variant titles,
which catches translations (<em>Studies on the development of consciousness</em> → Chesni,
<em>Recherches sur le développement de la conscience</em>). A stratified sample of 300 matches was
checked against LC’s own BIBFRAME Work links and, where LC links none, by hand.</p>
<table>
<thead>
<tr>
<th>confidence</th>
<th>records</th>
<th>exactly the right Hub</th>
<th>right work</th>
</tr>
</thead>
<tbody>
<tr>
<td>high (used on this page)</td>
<td>437,836</td>
<td><strong>96.5%</strong> (95% CI 93–98%)</td>
<td>99%</td>
</tr>
<tr>
<td>medium: several Hubs tie</td>
<td>30,425</td>
<td>66%</td>
<td>78%</td>
</tr>
<tr>
<td>low: title-only or name-only matches</td>
<td>25,070</td>
<td>66%</td>
<td>72%</td>
</tr>
</tbody>
</table>
<p>Two error patterns are labelled down rather than counted: titles that are just the creator’s
name (exhibition catalogues called <em>Picasso</em> gathered on one <code>Picasso, Pablo. Picasso</code> Hub),
and series whose volumes share a main title (<em>Chicken soup for the soul : …</em>), which show up
as one Hub spread over many classes with a different subtitle on almost every record. Method,
columns and every hand-checked row are on the
<a href="https://huggingface.co/datasets/jimfhahn/lc-bibframe-hubs#bib_to_hubparquet-details">dataset card</a>.</p>
</div></div><div id="cell-20" class="observablehq observablehq--cell"><div><hr>
<h2 id="about" tabindex="-1"><a href="#about">About</a></h2>
<p><strong>Data.</strong> Library of Congress BIBFRAME Hubs bulk dump (2026-05-05) and LC’s experimental
bibliographic embeddings (2025), both in the public domain, repackaged as Parquet:
<a href="https://huggingface.co/datasets/jimfhahn/lc-bibframe-hubs">lc-bibframe-hubs</a> (including
<code>bib_to_hub</code> and the four small <code>collocation/</code> files this page loads) and
<a href="https://huggingface.co/datasets/jimfhahn/lc-bib-embeddings">lc-bib-embeddings</a>.
“Creative relationship” means the surprise tiers 1–3 used by the companion VuFind plugin:
transformations and parodies, cross-medium adaptations, sequels and <em>based on</em>.</p>
<p><strong>Code.</strong> <a href="https://github.com/jimfhahn/vufind-bf-hubs-plugin/tree/main/tools/bib-hub-mapping"><code>tools/bib-hub-mapping/</code></a>
builds the match and these files; this page is
<a href="https://github.com/jimfhahn/vufind-bf-hubs-plugin/tree/main/notebooks"><code>notebooks/hub-collocation.html</code></a>
(<a href="https://observablehq.github.io/notebook-kit/">Observable Notebook Kit</a>). Tables loaded in
the browser: <code>hub_class(hub_id, label, hub_name, hub_title, lcc_class, n, hub_total, hub_classes, hub_links)</code>,
<code>edges(source_id, target_id, rel_type)</code>, <code>records(lc_001, hub_id, lcc_class, title, creator)</code>,
<code>coverage(lcc_class, records, high, medium, low)</code>.</p>
</div></div></main>
<style type="text/css">:root{--syntax-link:var(--theme-foreground-focus);--syntax-keyword:#e36209;--syntax-atom:#032f62;--syntax-literal:#6f42c1;--syntax-string:#22863a;--syntax-comment:var(--theme-foreground-muted);--syntax-invalid:var(--theme-error);--syntax-definition:#005cc5;--syntax-variable:var(--theme-foreground-alt);--syntax-meta:#d73a49;--theme-background-b:color-mix(in srgb, var(--theme-foreground) 4%, var(--theme-background-a));--theme-background:var(--theme-background-a);--theme-background-alt:var(--theme-background-b);--theme-foreground-alt:color-mix(in srgb, var(--theme-foreground) 90%, var(--theme-background-a));--theme-foreground-muted:color-mix(in srgb, var(--theme-foreground) 60%, var(--theme-background-a));--theme-foreground-faint:color-mix(in srgb, var(--theme-foreground) 50%, var(--theme-background-a));--theme-foreground-fainter:color-mix(in srgb, var(--theme-foreground) 30%, var(--theme-background-a));--theme-foreground-faintest:color-mix(in srgb, var(--theme-foreground) 14%, var(--theme-background-a));--theme-error:#e7040f;--lightningcss-light:initial;--lightningcss-dark: ;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--theme-foreground:#1b1e23;--theme-foreground-focus:#3b5fc0;--theme-background-a:#fff}</style></body></html>