Public
Edited
Jan 10
Insert cell
Insert cell
{
const data = snpNames.flatMap((name, i) => {
const q = mafs[i];
return [
{ name, type: "Homozygous reference", value: (1 - q) ** 2, order: 1 },
{ name, type: "Heterozygous", value: 2 * (1 - q) * q, order: 2 },
{ name, type: "Homozygous alternate", value: q ** 2, order: 3 }
];
});
console.log(data);

return Plot.plot({
marginLeft: 150,
height: snpNames.length * 9,
title: "Allele dosage distribution for European ancestry",
style: {
fontSize: "8px"
},
x: {
axis: "top",
grid: true,
label: "Distribution"
},
y: {
label: null,
domain: snpNames,
lineAnchor: "right",
padding: 0.1,
fontSize: 1
},
color: {
domain: ["Homozygous reference", "Heterozygous", "Homozygous alternate"],
range: ["#0297b8", "#eda6af", "#b2182b"],
legend: true
},
marks: [
Plot.barX(data, {
x: "value",
y: "name",
fill: "type",
order: "order",
tip: true,
title: (d) => `${d.type}: ${d.value.toFixed(3)}`
})
]
});
}
Insert cell
pgsData = loadPgsData(4, "GRCh37")
Insert cell
mafs = pgsData.data[pgsData.headers.indexOf("allelefrequency_effect")]
Insert cell
snpNames = {
const chrNames = pgsData.data[pgsData.headers.indexOf("chr_name")];
const chrPositions = pgsData.data[pgsData.headers.indexOf("chr_position")];
const effectAlleles = pgsData.data[pgsData.headers.indexOf("effect_allele")];
const otherAlleles = pgsData.data[pgsData.headers.indexOf("other_allele")];

const snpNames = chrNames?.map(
(_, i) =>
`${chrNames[i]}:${chrPositions[i]}:${effectAlleles[i]}:${otherAlleles[i]}`
);
return snpNames;
}
Insert cell
Insert cell
async function loadPgsData(id, build = "GRCh37") {
const pgsCatalogId = "PGS" + id.toString().padStart(6, "0");

try {
const url = `https://ftp.ebi.ac.uk/pub/databases/spot/pgs/scores/${pgsCatalogId}/ScoringFiles/Harmonized/${pgsCatalogId}_hmPOS_${build}.txt.gz`;

const text = await fetchGzippedFile(url);
const parsed = parseTsvContent(text);

return parsed;
} catch (error) {
console.error("Failed to load or parse file:", error);
throw error;
}
}
Insert cell
async function fetchGzippedFile(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const compressed = await response.arrayBuffer();
const ds = new DecompressionStream('gzip');
const compressedStream = new Blob([compressed])
.stream()
.pipeThrough(ds);

const decompressedResponse = new Response(compressedStream);
const text = await decompressedResponse.text();

return text;
} catch (error) {
console.error('Error fetching or decompressing file:', error);
throw error;
}
}
Insert cell
function parseTsvContent(content) {
const lines = content.split("\n");

const headerIndex = lines.findIndex(
(line) => !line.startsWith("#") && line.trim().length > 0
);
if (headerIndex === -1) {
throw new Error("No header line found in file");
}

const headers = lines[headerIndex].split("\t").map((header) => header.trim());
const numColumns = headers.length;

const data = Array(numColumns)
.fill()
.map(() => []);

lines
.slice(headerIndex + 1)
.filter((line) => line.trim().length > 0)
.forEach((line) => {
const values = line.split("\t");
values.forEach((value, colIndex) => {
if (colIndex < numColumns) {
const trimmedValue = value ? value.trim() : "";
data[colIndex].push(
!isNaN(trimmedValue) && trimmedValue !== ""
? Number(trimmedValue)
: trimmedValue
);
}
});
});

return {
headers,
data
};
}
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