@@ -1779,3 +1779,151 @@ def zmeta_reference_points_timeseries_output(self) -> Dict:
17791779 """
17801780 ds = self .reference_points_timeseries_output ()
17811781 return self ._zmeta (ds )
1782+
1783+ def reference_lines_flow (self , use_names : bool = False ) -> DataFrame :
1784+ """Return wide-format DataFrame for reference lines timeseries flow data.
1785+
1786+ Parameters
1787+ ----------
1788+ use_names : bool, optional
1789+ (Default) If False, use reference line IDs as column headers.
1790+ If True, use reference line names as column headers.
1791+
1792+ Returns
1793+ -------
1794+ DataFrame
1795+ Wide-format DataFrame with time as index and reference line IDs (or names) as columns.
1796+ """
1797+ ds = self .reference_lines_timeseries_output ()
1798+ return self ._timeseries_to_wide_dataframe (
1799+ ds = ds ,
1800+ var = "Flow" ,
1801+ id_column = "refln_id" ,
1802+ name_column = "refln_name" ,
1803+ mesh_column = "mesh_name" ,
1804+ use_names_as_col = use_names ,
1805+ )
1806+
1807+ def reference_points_stage (self , use_names : bool = False ) -> DataFrame :
1808+ """Return Wide-format DataFrame for reference points timeseries stage data.
1809+
1810+ Parameters
1811+ ----------
1812+ use_names : bool, optional
1813+ (Default) If False, use reference point IDs as column headers.
1814+ If True, use reference point names as column headers.
1815+
1816+ Returns
1817+ -------
1818+ DataFrame
1819+ Wide-format DataFrame with time as index and reference point IDs (or names) as columns.
1820+ """
1821+ ds = self .reference_points_timeseries_output ()
1822+ return self ._timeseries_to_wide_dataframe (
1823+ ds = ds ,
1824+ var = WATER_SURFACE ,
1825+ id_column = "refpt_id" ,
1826+ name_column = "refpt_name" ,
1827+ mesh_column = "mesh_name" ,
1828+ use_names_as_col = use_names ,
1829+ )
1830+
1831+ def bc_lines_flow (self , use_names : bool = False ) -> DataFrame :
1832+ """Return wide-format DataFrame for boundary condition lines timeseries flow data with.
1833+
1834+ Parameters
1835+ ----------
1836+ use_names : bool, optional
1837+ (Default) If False, use BC line IDs as column headers.
1838+ If True, use BC line names as column headers.
1839+
1840+ Returns
1841+ -------
1842+ DataFrame
1843+ Wide-format DataFrame with time as index and BC line IDs (or names) as columns.
1844+ """
1845+ ds = self .bc_lines_timeseries_output ()
1846+ return self ._timeseries_to_wide_dataframe (
1847+ ds = ds ,
1848+ var = "Flow" ,
1849+ id_column = "bc_line_id" ,
1850+ name_column = "bc_line_name" ,
1851+ mesh_column = "mesh_name" ,
1852+ use_names_as_col = use_names ,
1853+ )
1854+
1855+ def _timeseries_to_wide_dataframe (
1856+ self ,
1857+ ds : xr .Dataset ,
1858+ var : str ,
1859+ id_column : str ,
1860+ name_column : str ,
1861+ mesh_column : str ,
1862+ use_names_as_col : bool = False ,
1863+ ) -> DataFrame :
1864+ """Convert xarray timeseries Dataset to wide-format DataFrame with metadata.
1865+
1866+ Parameters
1867+ ----------
1868+ ds : xr.Dataset
1869+ xarray Dataset containing timeseries data
1870+ var : str
1871+ Variable name to extract (e.g. "Flow", "Water Surface")
1872+ id_column : str
1873+ ID column name for pivoting (e.g. "refln_id", "refpt_id", "bc_line_id")
1874+ name_column : str
1875+ Name column for creating readable column names (e.g. "refln_name", "refpt_name")
1876+ mesh_column : str
1877+ Mesh column name (e.g. "mesh_name")
1878+ use_names_as_col : bool, optional
1879+ (Default) If False, use IDs.
1880+ If True, use names as column headers.
1881+
1882+ Returns
1883+ -------
1884+ DataFrame
1885+ Wide-format DataFrame with time as index and IDs or names as columns.
1886+ Metadata stored in DataFrame.attrs including name and mesh mappings.
1887+ """
1888+ if var not in ds :
1889+ raise ValueError (f"{ var } data not found in timeseries output" )
1890+
1891+ df = ds [var ].to_dataframe ().dropna ().reset_index ()
1892+
1893+ # check for duplicate names when using names as columns
1894+ if use_names_as_col :
1895+ unique_names = df [name_column ].nunique ()
1896+ unique_ids = df [id_column ].nunique ()
1897+ if unique_names < unique_ids : # should have one name for every one id
1898+ name_counts = (
1899+ df [[id_column , name_column ]]
1900+ .drop_duplicates ()[name_column ]
1901+ .value_counts ()
1902+ )
1903+ duplicates = name_counts [name_counts > 1 ].index .tolist ()
1904+ raise ValueError (
1905+ f"Cannot use names as columns. The following names are not unique: { duplicates } . "
1906+ )
1907+
1908+ pivot_column = name_column if use_names_as_col else id_column
1909+ wide_df = df .pivot (index = "time" , columns = pivot_column , values = var )
1910+
1911+ lookup = df [[id_column , name_column , mesh_column ]].drop_duplicates ()
1912+ if use_names_as_col :
1913+ # when using names as columns, key=name -> value=id
1914+ id_mapping = lookup .set_index (name_column )[id_column ].to_dict ()
1915+ mesh_mapping = lookup .set_index (name_column )[mesh_column ].to_dict ()
1916+ else :
1917+ # when using IDs as columns, key=id -> value=name
1918+ id_mapping = lookup .set_index (id_column )[name_column ].to_dict ()
1919+ mesh_mapping = lookup .set_index (id_column )[mesh_column ].to_dict ()
1920+
1921+ wide_df .attrs = {
1922+ "variable" : var ,
1923+ "units" : ds [var ].attrs .get ("units" , None ),
1924+ "hdf_path" : ds [var ].attrs .get ("hdf_path" , None ),
1925+ "id_mapping" : id_mapping ,
1926+ "mesh_mapping" : mesh_mapping ,
1927+ }
1928+
1929+ return wide_df
0 commit comments