From f2e3407585862581b82680e8521b1e4d944f558a Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 6 Feb 2017 20:01:03 +0000 Subject: Fixed appearance of several tables (search, mapping results, sample data) Temporarily removed second sample table for CFW traits Fixed location of global search bar to work with wider screens --- wqflask/base/data_set.py | 143 ++++++++++++++++++++++++++--------------------- 1 file changed, 80 insertions(+), 63 deletions(-) (limited to 'wqflask/base') diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 94b38e13..30c8e996 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -273,6 +273,7 @@ class DatasetGroup(object): self.get_f1_parent_strains() self.accession_id = self.get_accession_id() + self.mapping_id, self.mapping_names = self.get_mapping_methods() self.species = webqtlDatabaseFunction.retrieve_species(self.name) @@ -295,6 +296,20 @@ class DatasetGroup(object): else: return "None" + def get_mapping_methods(self): + + mapping_id = g.db.execute("select MappingMethodId from InbredSet where Name= '%s'" % self.name).fetchone()[0] + if mapping_id == "1": + mapping_names = ["QTLReaper", "PYLMM", "R/qtl"] + elif mapping_id == "2": + mapping_names = ["GEMMA"] + elif mapping_id == "4": + mapping_names = ["PLINK"] + else: + mapping_names = [] + + return mapping_id, mapping_names + def get_specified_markers(self, markers = []): self.markers = HumanMarkers(self.name, markers) @@ -317,69 +332,6 @@ class DatasetGroup(object): self.markers = marker_class(self.name) - def datasets(self): - key = "group_dataset_menu:v2:" + self.name - logger.debug("key is2:", key) - dataset_menu = [] - logger.debug("[tape4] webqtlConfig.PUBLICTHRESH:", webqtlConfig.PUBLICTHRESH) - logger.debug("[tape4] type webqtlConfig.PUBLICTHRESH:", type(webqtlConfig.PUBLICTHRESH)) - the_results = fetchall(''' - (SELECT '#PublishFreeze',PublishFreeze.FullName,PublishFreeze.Name - FROM PublishFreeze,InbredSet - WHERE PublishFreeze.InbredSetId = InbredSet.Id - and InbredSet.Name = '%s' - and PublishFreeze.public > %s) - UNION - (SELECT '#GenoFreeze',GenoFreeze.FullName,GenoFreeze.Name - FROM GenoFreeze, InbredSet - WHERE GenoFreeze.InbredSetId = InbredSet.Id - and InbredSet.Name = '%s' - and GenoFreeze.public > %s) - UNION - (SELECT Tissue.Name, ProbeSetFreeze.FullName,ProbeSetFreeze.Name - FROM ProbeSetFreeze, ProbeFreeze, InbredSet, Tissue - WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id - and ProbeFreeze.TissueId = Tissue.Id - and ProbeFreeze.InbredSetId = InbredSet.Id - and InbredSet.Name like %s - and ProbeSetFreeze.public > %s - ORDER BY Tissue.Name, ProbeSetFreeze.CreateTime desc, ProbeSetFreeze.AvgId) - ''' % (self.name, webqtlConfig.PUBLICTHRESH, - self.name, webqtlConfig.PUBLICTHRESH, - "'" + self.name + "'", webqtlConfig.PUBLICTHRESH)) - - #for tissue_name, dataset in itertools.groupby(the_results, itemgetter(0)): - for dataset_item in the_results: - tissue_name = dataset_item[0] - dataset = dataset_item[1] - dataset_short = dataset_item[2] - if tissue_name in ['#PublishFreeze', '#GenoFreeze']: - dataset_menu.append(dict(tissue=None, datasets=[(dataset, dataset_short)])) - else: - dataset_sub_menu = [item[1:] for item in dataset] - - tissue_already_exists = False - tissue_position = None - for i, tissue_dict in enumerate(dataset_menu): - if tissue_dict['tissue'] == tissue_name: - tissue_already_exists = True - tissue_position = i - break - - if tissue_already_exists: - #logger.debug("dataset_menu:", dataset_menu[i]['datasets']) - dataset_menu[i]['datasets'].append((dataset, dataset_short)) - else: - dataset_menu.append(dict(tissue=tissue_name, - datasets=[(dataset, dataset_short)])) - - if USE_REDIS: - Redis.set(key, pickle.dumps(dataset_menu, pickle.HIGHEST_PROTOCOL)) - Redis.expire(key, 60*5) - self._datasets = dataset_menu - - return self._datasets - def get_f1_parent_strains(self): try: # NL, 07/27/2010. ParInfo has been moved from webqtlForm.py to webqtlUtil.py; @@ -456,6 +408,71 @@ class DatasetGroup(object): return genotype +def datasets(group_name, this_group = None): + key = "group_dataset_menu:v2:" + group_name + logger.debug("key is2:", key) + dataset_menu = [] + logger.debug("[tape4] webqtlConfig.PUBLICTHRESH:", webqtlConfig.PUBLICTHRESH) + logger.debug("[tape4] type webqtlConfig.PUBLICTHRESH:", type(webqtlConfig.PUBLICTHRESH)) + the_results = fetchall(''' + (SELECT '#PublishFreeze',PublishFreeze.FullName,PublishFreeze.Name + FROM PublishFreeze,InbredSet + WHERE PublishFreeze.InbredSetId = InbredSet.Id + and InbredSet.Name = '%s' + and PublishFreeze.public > %s) + UNION + (SELECT '#GenoFreeze',GenoFreeze.FullName,GenoFreeze.Name + FROM GenoFreeze, InbredSet + WHERE GenoFreeze.InbredSetId = InbredSet.Id + and InbredSet.Name = '%s' + and GenoFreeze.public > %s) + UNION + (SELECT Tissue.Name, ProbeSetFreeze.FullName,ProbeSetFreeze.Name + FROM ProbeSetFreeze, ProbeFreeze, InbredSet, Tissue + WHERE ProbeSetFreeze.ProbeFreezeId = ProbeFreeze.Id + and ProbeFreeze.TissueId = Tissue.Id + and ProbeFreeze.InbredSetId = InbredSet.Id + and InbredSet.Name like %s + and ProbeSetFreeze.public > %s + ORDER BY Tissue.Name, ProbeSetFreeze.CreateTime desc, ProbeSetFreeze.AvgId) + ''' % (group_name, webqtlConfig.PUBLICTHRESH, + group_name, webqtlConfig.PUBLICTHRESH, + "'" + group_name + "'", webqtlConfig.PUBLICTHRESH)) + + #for tissue_name, dataset in itertools.groupby(the_results, itemgetter(0)): + for dataset_item in the_results: + tissue_name = dataset_item[0] + dataset = dataset_item[1] + dataset_short = dataset_item[2] + if tissue_name in ['#PublishFreeze', '#GenoFreeze']: + dataset_menu.append(dict(tissue=None, datasets=[(dataset, dataset_short)])) + else: + dataset_sub_menu = [item[1:] for item in dataset] + + tissue_already_exists = False + tissue_position = None + for i, tissue_dict in enumerate(dataset_menu): + if tissue_dict['tissue'] == tissue_name: + tissue_already_exists = True + tissue_position = i + break + + if tissue_already_exists: + #logger.debug("dataset_menu:", dataset_menu[i]['datasets']) + dataset_menu[i]['datasets'].append((dataset, dataset_short)) + else: + dataset_menu.append(dict(tissue=tissue_name, + datasets=[(dataset, dataset_short)])) + + if USE_REDIS: + Redis.set(key, pickle.dumps(dataset_menu, pickle.HIGHEST_PROTOCOL)) + Redis.expire(key, 60*5) + + if this_group != None: + this_group._datasets = dataset_menu + return this_group._datasets + else: + return dataset_menu class DataSet(object): """ -- cgit v1.2.3 From 434ce88d3b7bf29a65e738aa171a7bba6a887471 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 9 Feb 2017 21:50:54 +0000 Subject: Added genetic_type to DatasetGroup object to differentiate between which groups should show dominance effect Minor changes/improvements for trait page Updated dataset_menu_structure.json --- wqflask/base/data_set.py | 4 +- wqflask/wqflask/do_search.py | 7 +- wqflask/wqflask/show_trait/show_trait.py | 8 +- .../new/javascript/dataset_menu_structure.json | 150 ++++++++++----------- .../static/new/javascript/draw_probability_plot.js | 12 +- .../wqflask/templates/marker_regression_gn1.html | 29 +++- wqflask/wqflask/templates/show_trait_details.html | 42 +++--- .../wqflask/templates/show_trait_statistics.html | 4 +- 8 files changed, 139 insertions(+), 117 deletions(-) (limited to 'wqflask/base') diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 02b5e07c..4b3ee4ca 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -264,7 +264,7 @@ class DatasetGroup(object): def __init__(self, dataset): """This sets self.group and self.group_id""" #logger.debug("DATASET NAME2:", dataset.name) - self.name, self.id = fetchone(dataset.query_for_group) + self.name, self.id, self.genetic_type = fetchone(dataset.query_for_group) if self.name == 'BXD300': self.name = "BXD" @@ -965,7 +965,7 @@ class MrnaAssayDataSet(DataSet): self.query_for_group = ''' SELECT - InbredSet.Name, InbredSet.Id + InbredSet.Name, InbredSet.Id, InbredSet.GeneticType FROM InbredSet, ProbeSetFreeze, ProbeFreeze WHERE diff --git a/wqflask/wqflask/do_search.py b/wqflask/wqflask/do_search.py index ad3eab79..e1df1e63 100644 --- a/wqflask/wqflask/do_search.py +++ b/wqflask/wqflask/do_search.py @@ -826,7 +826,8 @@ class PositionSearch(DoSearch): def get_where_clause(self): self.search_term = [float(value) if is_number(value) else value for value in self.search_term] - self.chr, self.mb_min, self.mb_max = self.search_term[:3] + chr, self.mb_min, self.mb_max = self.search_term[:3] + self.chr = str(chr).lower() self.get_chr() where_clause = """ %s.Chr = %s and @@ -846,9 +847,9 @@ class PositionSearch(DoSearch): self.chr = int(self.chr) except: if 'chr' in self.chr: - self.chr = int(self.chr.replace('chr', '')) + self.chr = self.chr.replace('chr', '') else: - self.chr = int(self.chr.replace('CHR', '')) + self.chr = self.chr.replace('CHR', '') def run(self): diff --git a/wqflask/wqflask/show_trait/show_trait.py b/wqflask/wqflask/show_trait/show_trait.py index 3e145e40..5d55ebe1 100644 --- a/wqflask/wqflask/show_trait/show_trait.py +++ b/wqflask/wqflask/show_trait/show_trait.py @@ -118,7 +118,13 @@ class ShowTrait(object): self.trait_table_width = get_trait_table_width(self.sample_groups) - js_data = dict(dataset_type = self.dataset.type, + if self.this_trait.symbol: + trait_symbol = self.this_trait.symbol + else: + trait_symbol = None + js_data = dict(trait_id = self.this_trait.name, + trait_symbol = trait_symbol, + dataset_type = self.dataset.type, data_scale = self.dataset.data_scale, sample_group_types = self.sample_group_types, sample_lists = sample_lists, diff --git a/wqflask/wqflask/static/new/javascript/dataset_menu_structure.json b/wqflask/wqflask/static/new/javascript/dataset_menu_structure.json index ad4d9419..861af3d6 100644 --- a/wqflask/wqflask/static/new/javascript/dataset_menu_structure.json +++ b/wqflask/wqflask/static/new/javascript/dataset_menu_structure.json @@ -116,15 +116,15 @@ "B30_K_1206_M", "Barley1 Leaf MAS 5.0 SCRI (Dec06)" ], - [ - "125", - "B30_K_1206_R", - "Barley1 Leaf gcRMA SCRI (Dec06)" - ], [ "126", "B30_K_1206_Rn", "Barley1 Leaf gcRMAn SCRI (Dec06)" + ], + [ + "125", + "B30_K_1206_R", + "Barley1 Leaf gcRMA SCRI (Dec06)" ] ], "Phenotypes": [ @@ -1525,15 +1525,15 @@ "NCI_Agil_Mam_Tum_RMA_0409", "NCI Mammary LMT miRNA v2 (Apr09) RMA" ], - [ - "36", - "MA_M_0704_M", - "NCI Mammary mRNA M430 (July04) MAS5" - ], [ "37", "MA_M_0704_R", "NCI Mammary mRNA M430 (July04) RMA" + ], + [ + "36", + "MA_M_0704_M", + "NCI Mammary mRNA M430 (July04) MAS5" ] ] }, @@ -1590,15 +1590,15 @@ ] ], "Liver mRNA": [ - [ - "38", - "LVF2_M_0704_M", - "(B6 x BTBR)F2-ob/ob Liver mRNA M430 (Jul04) MAS5" - ], [ "39", "LVF2_M_0704_R", "(B6 x BTBR)F2-ob/ob Liver mRNA M430 (Jul04) RMA" + ], + [ + "38", + "LVF2_M_0704_M", + "(B6 x BTBR)F2-ob/ob Liver mRNA M430 (Jul04) MAS5" ] ], "Phenotypes": [ @@ -1620,11 +1620,6 @@ }, "B6D2F2": { "Brain mRNA": [ - [ - "78", - "BRF2_M_0805_P", - "OHSU/VA B6D2F2 Brain mRNA M430 (Aug05) PDNN" - ], [ "77", "BRF2_M_0805_R", @@ -1635,20 +1630,25 @@ "BRF2_M_0805_M", "OHSU/VA B6D2F2 Brain mRNA M430 (Aug05) MAS5" ], + [ + "78", + "BRF2_M_0805_P", + "OHSU/VA B6D2F2 Brain mRNA M430 (Aug05) PDNN" + ], [ "33", "BRF2_M_0304_P", "OHSU/VA B6D2F2 Brain mRNA M430A (Mar04) PDNN" ], - [ - "31", - "BRF2_M_0304_M", - "OHSU/VA B6D2F2 Brain mRNA M430A (Mar04) MAS5" - ], [ "32", "BRF2_M_0304_R", "OHSU/VA B6D2F2 Brain mRNA M430A (Mar04) RMA" + ], + [ + "31", + "BRF2_M_0304_M", + "OHSU/VA B6D2F2 Brain mRNA M430A (Mar04) MAS5" ] ], "Genotypes": [ @@ -1726,11 +1726,6 @@ ] ], "Striatum mRNA": [ - [ - "83", - "SA_M2_0905_M", - "OHSU/VA B6D2F2 Striatum M430v2 (Sep05) MAS5" - ], [ "85", "SA_M2_0905_P", @@ -1740,6 +1735,11 @@ "84", "SA_M2_0905_R", "OHSU/VA B6D2F2 Striatum M430v2 (Sep05) RMA" + ], + [ + "83", + "SA_M2_0905_M", + "OHSU/VA B6D2F2 Striatum M430v2 (Sep05) MAS5" ] ] }, @@ -2080,11 +2080,6 @@ "Eye_M2_0908_R_ND", "Eye M430v2 WT Gpnmb (Sep08) RMA" ], - [ - "382", - "Eye_M2_0908_WTWT", - "Eye M430v2 WT WT (Sep08) RMA" - ], [ "279", "Eye_M2_0908_R_WT", @@ -2095,6 +2090,11 @@ "Eye_M2_0908_R_MT", "Eye M430v2 Mutant Tyrp1 (Sep08) RMA" ], + [ + "382", + "Eye_M2_0908_WTWT", + "Eye M430v2 WT WT (Sep08) RMA" + ], [ "400", "DBA2J-ONH-1212", @@ -2331,16 +2331,16 @@ ] ], "Kidney mRNA": [ - [ - "240", - "MA_M2M_0706_R", - "Mouse kidney M430v2 Male (Aug06) RMA" - ], [ "239", "MA_M2F_0706_R", "Mouse kidney M430v2 Female (Aug06) RMA" ], + [ + "240", + "MA_M2M_0706_R", + "Mouse kidney M430v2 Male (Aug06) RMA" + ], [ "118", "MA_M2_0806_R", @@ -2351,15 +2351,15 @@ "MA_M2_0806_P", "Mouse Kidney M430v2 Sex Balanced (Aug06) PDNN" ], - [ - "115", - "MA_M2_0706_R", - "Mouse Kidney M430v2 (Jul06) RMA" - ], [ "116", "MA_M2_0706_P", "Mouse Kidney M430v2 (Jul06) PDNN" + ], + [ + "115", + "MA_M2_0706_R", + "Mouse Kidney M430v2 (Jul06) RMA" ] ], "Liver Metabolome": [ @@ -2520,16 +2520,16 @@ ] ], "Neocortex mRNA": [ - [ - "374", - "DevNeocortex_ILM6.2P3RInv_1111", - "BIDMC/UTHSC Dev Neocortex P3 ILMv6.2 (Nov11) RankInv" - ], [ "375", "DevNeocortex_ILM6.2P14RInv_1111", "BIDMC/UTHSC Dev Neocortex P14 ILMv6.2 (Nov11) RankInv" ], + [ + "374", + "DevNeocortex_ILM6.2P3RInv_1111", + "BIDMC/UTHSC Dev Neocortex P3 ILMv6.2 (Nov11) RankInv" + ], [ "284", "HQFNeoc_1210v2_RankInv", @@ -2760,11 +2760,6 @@ ] ], "Ventral Tegmental Area mRNA": [ - [ - "228", - "VCUSal_0609_R", - "VCU BXD VTA Sal M430 2.0 (Jun09) RMA" - ], [ "230", "VCUEtvsSal_0609_R", @@ -2774,6 +2769,11 @@ "229", "VCUEtOH_0609_R", "VCU BXD VTA EtOH M430 2.0 (Jun09) RMA" + ], + [ + "228", + "VCUSal_0609_R", + "VCU BXD VTA Sal M430 2.0 (Jun09) RMA" ] ] }, @@ -3022,15 +3022,15 @@ ] ], "Hippocampus mRNA": [ - [ - "99", - "HC_M2CB_1205_P", - "Hippocampus Consortium M430v2 CXB (Dec05) PDNN" - ], [ "100", "HC_M2CB_1205_R", "Hippocampus Consortium M430v2 CXB (Dec05) RMA" + ], + [ + "99", + "HC_M2CB_1205_P", + "Hippocampus Consortium M430v2 CXB (Dec05) PDNN" ] ], "Phenotypes": [ @@ -3096,16 +3096,6 @@ ] ], "Hippocampus mRNA": [ - [ - "214", - "Illum_LXS_Hipp_NOE_1008", - "Hippocampus Illumina NOE (Oct08) RankInv beta" - ], - [ - "211", - "Illum_LXS_Hipp_RSS_1008", - "Hippocampus Illumina RSS (Oct08) RankInv beta" - ], [ "213", "Illum_LXS_Hipp_NOS_1008", @@ -3121,6 +3111,16 @@ "Illum_LXS_Hipp_RSE_1008", "Hippocampus Illumina RSE (Oct08) RankInv beta" ], + [ + "214", + "Illum_LXS_Hipp_NOE_1008", + "Hippocampus Illumina NOE (Oct08) RankInv beta" + ], + [ + "211", + "Illum_LXS_Hipp_RSS_1008", + "Hippocampus Illumina RSS (Oct08) RankInv beta" + ], [ "143", "Illum_LXS_Hipp_loess0807", @@ -3211,15 +3211,15 @@ ] ], "Hippocampus mRNA": [ - [ - "272", - "HC_M2_0606_MDP", - "Hippocampus Consortium M430v2 (Jun06) RMA MDP" - ], [ "273", "UMUTAffyExon_0209_RMA_MDP", "UMUTAffy Hippocampus Exon (Feb09) RMA MDP" + ], + [ + "272", + "HC_M2_0606_MDP", + "Hippocampus Consortium M430v2 (Jun06) RMA MDP" ] ], "Liver mRNA": [ diff --git a/wqflask/wqflask/static/new/javascript/draw_probability_plot.js b/wqflask/wqflask/static/new/javascript/draw_probability_plot.js index 1eeb6e73..e5787564 100644 --- a/wqflask/wqflask/static/new/javascript/draw_probability_plot.js +++ b/wqflask/wqflask/static/new/javascript/draw_probability_plot.js @@ -45,8 +45,7 @@ chart = nv.models.scatterChart().width(w).height(h).showLegend(true).color(d3.scale.category10().range()); chart.pointRange([50, 50]); chart.legend.updateState(false); - chart.xAxis.axisLabel("Theoretical quantiles").tickFormat(d3.format('.02f')); - //chart.yAxis.axisLabel("Sample quantiles").tickFormat(d3.format('.02f')); + chart.xAxis.axisLabel("Expected Z score").tickFormat(d3.format('.02f')); chart.tooltipContent(function(obj) { return '' + obj.point.name + ''; }); @@ -79,7 +78,7 @@ } return results; })(); - chart.yAxis.axisLabel("Sample quantiles").tickFormat(d3.format('.0'+max_decimals.toString()+'f')); + chart.yAxis.axisLabel("Trait value").tickFormat(d3.format('.0'+max_decimals.toString()+'f')); sw_result = ShapiroWilkW(sorted_values); W = sw_result.w.toFixed(3); pvalue = sw_result.p.toFixed(3); @@ -115,7 +114,12 @@ data = [make_data('samples_primary'), make_data('samples_other')]; console.log("THE DATA IS:", data); d3.select("#prob_plot_container svg").datum(data).call(chart); - $("#prob_plot_title").html("
Species | -{{ this_trait.dataset.group.species }} | -||
Group | -{{ this_trait.dataset.group.name }} | +Species and Group | +{{ this_trait.dataset.group.species }}, {{ this_trait.dataset.group.name }} |
Tissue | {{ this_trait.dataset.tissue }} | ||
Gene Symbol | +{{ this_trait.symbol }} | +||
Aliases | {{ this_trait.alias_fmt|replace(",",";") }} | @@ -74,49 +76,35 @@ -- cgit v1.2.3 From 2b14b0d04387a262f9895ddd87ce465c6835fa8c Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 9 Feb 2017 22:36:52 +0000 Subject: Fixed issue where I forgot to add GeneticType to the query_for_group query for non-mRNA Assay datasets Increased digits of some Basic Statistics values from 2 to 3 --- wqflask/base/data_set.py | 4 ++-- wqflask/wqflask/static/new/javascript/show_trait.js | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'wqflask/base') diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 4b3ee4ca..54dd3c4b 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -696,7 +696,7 @@ class PhenotypeDataSet(DataSet): self.query_for_group = ''' SELECT - InbredSet.Name, InbredSet.Id + InbredSet.Name, InbredSet.Id, InbredSet.GeneticType FROM InbredSet, PublishFreeze WHERE @@ -839,7 +839,7 @@ class GenotypeDataSet(DataSet): self.query_for_group = ''' SELECT - InbredSet.Name, InbredSet.Id + InbredSet.Name, InbredSet.Id, InbredSet.GeneticType FROM InbredSet, GenoFreeze WHERE diff --git a/wqflask/wqflask/static/new/javascript/show_trait.js b/wqflask/wqflask/static/new/javascript/show_trait.js index c7f9c562..8223a38c 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait.js +++ b/wqflask/wqflask/static/new/javascript/show_trait.js @@ -18,19 +18,19 @@ }, { vn: "mean", pretty: "Mean", - digits: 2 + digits: 3 }, { vn: "median", pretty: "Median", - digits: 2 + digits: 3 }, { vn: "std_error", pretty: "Standard Error (SE)", - digits: 2 + digits: 3 }, { vn: "std_dev", pretty: "Standard Deviation (SD)", - digits: 2 + digits: 3 }, { vn: "min", pretty: "Minimum", @@ -42,16 +42,16 @@ }, { vn: "range", pretty: "Range (log2)", - digits: 2 + digits: 3 }, { vn: "range_fold", pretty: "Range (fold)", - digits: 2 + digits: 3 }, { vn: "interquartile", pretty: "Interquartile Range", url: "http://www.genenetwork.org/glossary.html#Interquartile", - digits: 2 + digits: 3 } ]; -- cgit v1.2.3