Skip to content

Commit

Permalink
feature-table summarize refactor (#306)
Browse files Browse the repository at this point in the history
  • Loading branch information
Oddant1 authored May 24, 2024
1 parent 55e67a2 commit 4e484ec
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 77 deletions.
47 changes: 24 additions & 23 deletions q2_feature_table/_summarize/_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,41 +151,42 @@ def summarize(output_dir: str, table: biom.Table,
feature_frequencies_ax.get_figure().savefig(
os.path.join(output_dir, 'feature-frequencies.png'))

sample_summary_table = q2templates.df_to_html(
sample_summary.apply('{:,}'.format).to_frame('Frequency'))
feature_summary_table = q2templates.df_to_html(
feature_summary.apply('{:,}'.format).to_frame('Frequency'))
sample_summary_json = pd.DataFrame(
sample_summary, columns=['Frequency']).to_json()
feature_summary_json = pd.DataFrame(
feature_summary, columns=['Frequency']).to_json()

index = os.path.join(TEMPLATES, 'summarize_assets', 'index.html')
context = {
'number_of_samples': number_of_samples,
'number_of_features': number_of_features,
'total_frequencies': int(np.sum(sample_frequencies)),
'sample_summary_table': sample_summary_table,
'feature_summary_table': feature_summary_table,
'sample_summary_table': sample_summary_json,
'feature_summary_table': feature_summary_json,
}

feature_qualitative_data = _compute_qualitative_summary(table)
sample_frequencies.sort_values(inplace=True, ascending=False)

sample_frequencies_json = pd.Series(["{:,}".format(int(x)) for x in
sample_frequencies],
index=sample_frequencies.index)

feature_frequencies.sort_values(inplace=True, ascending=False)
# Create a JSON object containing the Sample Frequencies to build the
# table in sample-frequency-detail.html
#
# Cast to DataFrame to standardize with other tables
sample_frequencies_json = pd.DataFrame(
sample_frequencies, columns=['Frequency']).to_json()

feature_frequencies = feature_frequencies.astype(int) \
.apply('{:,}'.format).to_frame('Frequency')
# Create a JSON object containing the Feature Frequencies to build the
# table in feature-frequency-detail.html
feature_qualitative_data = _compute_qualitative_summary(table)
feature_frequencies = feature_frequencies.astype(int).to_frame('Frequency')
feature_frequencies['# of Samples Observed In'] = \
pd.Series(feature_qualitative_data).astype(int).apply('{:,}'.format)
feature_frequencies_table = q2templates.df_to_html(feature_frequencies)
pd.Series(feature_qualitative_data).astype(int)
feature_frequencies_json = feature_frequencies.to_json()

sample_frequency_template = os.path.join(
TEMPLATES, 'summarize_assets', 'sample-frequency-detail.html')
feature_frequency_template = os.path.join(
TEMPLATES, 'summarize_assets', 'feature-frequency-detail.html')

context.update({'max_count': sample_frequencies.max(),
'feature_frequencies_table': feature_frequencies_table,
'feature_frequencies_json': feature_frequencies_json,
'feature_qualitative_data': feature_qualitative_data,
'tabs': [{'url': 'index.html',
'title': 'Overview'},
Expand All @@ -194,10 +195,6 @@ def summarize(output_dir: str, table: biom.Table,
{'url': 'feature-frequency-detail.html',
'title': 'Feature Detail'}]})

# Create a JSON object containing the Sample Frequencies to build the
# table in sample-frequency-detail.html
sample_frequencies_json = sample_frequencies_json.to_json()

templates = [index, sample_frequency_template, feature_frequency_template]
context.update({'frequencies_list':
json.dumps(sorted(sample_frequencies.values.tolist()))})
Expand All @@ -212,6 +209,10 @@ def summarize(output_dir: str, table: biom.Table,
'summarize_assets',
'vega'),
output_dir)
q2templates.util.copy_assets(os.path.join(TEMPLATES,
'summarize_assets',
'utils'),
output_dir)
q2templates.render(templates, output_dir, context=context)

plt.close('all')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
{% extends "tabbed.html" %}

{% extends "tabbed.html" %} {% block head %}
<script src="util.js"></script> {% endblock %}
{% block tabcontent %}
<div class="row">
<div class="row">
<div class="col-lg-12">
<div class="col-lg-12">
{{ feature_frequencies_table }}
<table class="table table-striped table-hover" border="0">
<thead>
<tr style="text-align: right;">
<th></th>
<th>Frequency</th>
<th># of Samples Observed In</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
</div>
</div>
</div>

<script id="table-data" type="application/json">
{{ feature_frequencies_json }}
</script>

<script type="text/javascript">
const tableBody = document.getElementById("table-body");
const tableData = JSON.parse(document.getElementById("table-data").innerHTML);
const featureFrequencies = tableData["Frequency"];

const sortedFeatureIDs = Object.keys(featureFrequencies).sort(function(a, b) {
return sortIDs(a, b, featureFrequencies)
});

formatTable(tableBody, tableData, sortedFeatureIDs);
</script>

{% endblock %}
67 changes: 57 additions & 10 deletions q2_feature_table/_summarize/summarize_assets/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "tabbed.html" %}

{% extends "tabbed.html" %} {% block head %}
<script src="util.js"></script> {% endblock %}
{% block tabcontent %}
<div class="row">
<div class="col-lg-12">
Expand All @@ -10,22 +10,22 @@ <h1>Table summary</h1>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Metric</th>
<th>Sample</th>
<th>Summary Statistic</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Number of samples</th>
<td>{{ "{:,}".format(number_of_samples) }}</td>
<td id="number-of-samples"></td>
</tr>
<tr>
<th scope="row">Number of features</th>
<td>{{ "{:,}".format(number_of_features) }}</td>
<td id="number-of-features"></td>
</tr>
<tr>
<th scope="row">Total frequency</th>
<td>{{ "{:,}".format(total_frequencies) }}</td>
<td id="total-frequency"></td>
</tr>
</tbody>
</table>
Expand All @@ -36,7 +36,15 @@ <h1>Frequency per sample</h1>

<div class="row">
<div class="col-lg-6">
{{ sample_summary_table }}
<table class="table table-striped table-hover">
<thead>
<tr>
<th></th>
<th>Frequency</th>
</tr>
</thead>
<tbody id="sample-table-body"></tbody>
</table>
</div>
{% if number_of_samples > 1 %}
<div class="col-lg-6">
Expand All @@ -51,10 +59,19 @@ <h1>Frequency per sample</h1>
{% endif %}
</div>

<h1>Frequency per feature</h1>

<div class="row">
<div class="col-lg-6">
<h1>Frequency per feature</h1>
{{ feature_summary_table }}
<table class="table table-striped table-hover">
<thead>
<tr>
<th></th>
<th>Frequency</th>
</tr>
</thead>
<tbody id="feature-table-body"></tbody>
</table>
</div>
{% if number_of_features > 1 %}
<div class="col-lg-6">
Expand All @@ -70,4 +87,34 @@ <h1>Frequency per feature</h1>
</div>
</div>
</div>

<script id="sample-table-data" type="application/json">
{{ sample_summary_table }}
</script>

<script id="feature-table-data" type="application/json">
{{ feature_summary_table }}
</script>

<script type="text/javascript">
const numberOfSamples = document.getElementById("number-of-samples");
numberOfSamples.innerText = formatter.format({{number_of_samples}});

const numberOfFeatures = document.getElementById("number-of-features");
numberOfFeatures.innerText = formatter.format({{number_of_features}});

const totalFrequency = document.getElementById("total-frequency");
totalFrequency.innerText = formatter.format({{total_frequencies}});

const sampleTableBody = document.getElementById("sample-table-body");
const sampleTableData = JSON.parse(document.getElementById("sample-table-data").innerHTML);

formatTable(sampleTableBody, sampleTableData, Object.keys(sampleTableData["Frequency"]).reverse());

const featureTableBody = document.getElementById("feature-table-body");
const featureTableData = JSON.parse(document.getElementById("feature-table-data").innerHTML);

formatTable(featureTableBody, featureTableData, Object.keys(featureTableData["Frequency"]).reverse());
</script>

{% endblock %}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% extends "tabbed.html" %} {% block head %}
<script src="js/vega.min.js"></script>
<script src="js/vega-embed.min.js"></script>
<script src="util.js"></script>
<link rel="stylesheet" type="text/css" href="css/spinkit.css" /> {% endblock %}
{% block tabcontent %}

Expand Down Expand Up @@ -148,7 +149,7 @@ <h3>Plot Controls</h3>
<thead>
<tr>
<th scope="col">Sample ID</th>
<th scope="col"> Frequency </th>
<th scope="col">Frequency</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
Expand All @@ -175,43 +176,27 @@ <h3>Plot Controls</h3>
// when the viz loads the default description is displayed
textField.innerHTML = defaultDescription;

var sampleFrequency = JSON.parse(document.getElementById("table-data").innerHTML);
let sampleTable = JSON.parse(document.getElementById("table-data").innerHTML);
let sampleFrequencies = sampleTable["Frequency"];
// get object keys and store them in an ascending order based on the key value
// this order is used to create the table rows
sortedSampleIDs = Object.keys(sampleFrequency).sort(function(a, b) {
// The values of sampleFrequency are now strings of the form "1,234" as
// opposed to the numerical value "1234", so in order to compare these
// based on numerical value we get rid of the commas and cast to int in
// order to subtract normally
const aVal = strToInt(sampleFrequency[a]);
const bVal = strToInt(sampleFrequency[b]);
const diff = aVal - bVal;
// if two samples have the same number of features then we
// determine the order using the sample ID alphabetical order
if (diff == 0){
return b.localeCompare(a);
}

return diff;
});

sortedSampleIDs.forEach(function(element) {
var row = tableBody.insertRow(0);
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
cell1.innerHTML = element;
cell2.innerHTML = sampleFrequency[element];

sortedSampleIDs = Object.keys(sampleFrequencies).sort(function(a, b) {
return sortIDs(a, b, sampleFrequencies)
});

formatTable(tableBody, sampleTable, sortedSampleIDs);

function updateTableandText(samplingDepth) {
var retainedSampleCount = 0;
let retainedSampleCount = 0;

// start the counter at 1 to ignore the header row
for (var i = 1; row = table.rows[i]; i++) {
let sampleFrequency = row.cells[1].innerHTML;
sampleFrequency = strToInt(sampleFrequency)
// The value in table is formatted as a string for display to the user,
// but the value in sampleFrequencies is formatted as an int for data
// manipulation, so we use the ID we are on in table to get the frequency
// from sampleFrequencies.
const sampleID = row.cells[0].innerText;
const sampleFrequency = sampleFrequencies[sampleID];

if (sampleFrequency < samplingDepth) {
row.className = "danger";
Expand All @@ -221,18 +206,15 @@ <h3>Plot Controls</h3>
}
}
if (samplingDepth == 0){

textField.innerHTML = defaultDescription;

textField.innerHTML = defaultDescription;
}
else{
var retainedFeatureCount = retainedSampleCount * samplingDepth;
textField.innerHTML = "Retained " + retainedFeatureCount.toLocaleString('en')
textField.innerHTML = "Retained " + formatter.format(retainedFeatureCount)
+ " (" + (retainedFeatureCount/totalFrequencies*100).toFixed(2) + "%) features in "
+ retainedSampleCount + " (" + (retainedSampleCount/sampleCount*100).toFixed(2)
+ "%) samples at the specifed sampling depth.";
}

}


Expand Down Expand Up @@ -269,11 +251,6 @@ <h3>Plot Controls</h3>
}
updateSliderVal(val);
}

// Parse a string that may contain commas into a base 10 integer
function strToInt(val) {
return parseInt(val.replaceAll(",", ""), 10);
}
</script>

{% endblock %}
Expand Down
33 changes: 33 additions & 0 deletions q2_feature_table/_summarize/summarize_assets/utils/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Formats the number in a manner based on the locale but using latin numerals
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
const formatter = new Intl.NumberFormat({numberingSystem: "latn"})


function formatTable(tableBody, data, sortedKeys) {
for (const key of sortedKeys) {
let colIdx = 1;
let row = tableBody.insertRow(0);

let keyCell = row.insertCell(0)
keyCell.innerText = key;
keyCell.style.setProperty("text-align", "left");
keyCell.style.setProperty("font-weight", "bold");

for (const innerKey of Object.keys(data)) {
let dataCell = row.insertCell(colIdx++);
dataCell.innerText = formatter.format(data[innerKey][key]);
}
}
}


function sortIDs(a, b, data) {
const diff = data[a] - data[b];
// if two samples have the same number of features then we
// determine the order using the sample ID alphabetical order
if (diff == 0){
return b.localeCompare(a);
}

return diff;
}
2 changes: 1 addition & 1 deletion q2_feature_table/tests/test_summarize.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ def test_basic(self):
tbl_rx = re.compile(rx)
tbl = tbl_rx.findall(text)[0].split('\n')[1].strip()

sample_ids = json.loads(tbl).keys()
sample_ids = json.loads(tbl)['Frequency'].keys()

self.assertTrue('S1' in sample_ids)
self.assertTrue('S2' in sample_ids)
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
'summarize_assets/vega/licenses/*',
'summarize_assets/vega/js/*',
'summarize_assets/vega/css/*',
'summarize_assets/utils/*',
'tabulate_seqs_assets/js/*',
'tabulate_seqs_assets/index.html'],
'q2_feature_table._core_features': [
Expand Down

0 comments on commit 4e484ec

Please sign in to comment.