eshangj/stackoverflow_q_and_a_sample huggingface dataset:
Question: Update:  PanicException was fixed by pull/18249 in Polars 1.6.0 Broadcast length was fixed by pull/19148 in Polars 1.10.0  shape: (1, 1) ┌───────────┐ │ meta_data │ │ --- │ │ struct[0] │ ╞═══════════╡ │ {} │ └───────────┘   I want to make the string of empty dictionary to be a struct, but json_decode fails as there are some rows with strings of empty dictionary in a df import polars as pl df = pl.DataFrame({ "meta_data": ["{}"] }) df.with_columns(pl.col('meta_data').str.json_decode())  giving me, PanicException: called `Result::unwrap()` on an `Err` value: ComputeError(ErrString("a StructArray must contain at least one field"))  edit - the column has more string containing key value pairs also, df = pl.DataFrame({'a': ['{}', '{"b": "c"}']}) df = df.with_columns(pl.col("a").str.json_decode())  the above one is working fine, but when I keep only '{}', then it breaks

Answer: At the moment Polars doesn't support structs without fields - see related issue. So it cannot work with only empty json objects. However, if you know which fields can be in your data you can supply appropriate dtype parameter to pl.Expr.str.json_decode(): df = pl.DataFrame({"a": ["{}"]}) df.with_columns( pl.col('a').str.json_decode(dtype=pl.Struct({"b": pl.Utf8})) )  shape: (1, 1) ┌───────────┐ │ a │ │ --- │ │ struct[1] │ ╞═══════════╡ │ {null} │ └───────────┘  Which would also work for rows with non-empty data: df = pl.DataFrame({'a': ['{}', '{"b": "c"}']}) print(df.with_columns( pl.col('a').str.json_decode(dtype=pl.Struct({"b": pl.Utf8})) ))  shape: (2, 1) ┌───────────┐ │ a │ │ --- │ │ struct[1] │ ╞═══════════╡ │ {null} │ │ {"c"} │ └───────────┘  If you don't know the data in advance then you cannot provide proper dtype parameter. In this case polars will try to determine schema based on data, using infer_schema_length parameter. By default it would check first 100 values to define schema. If there's no meaningful values within the rows defined by infer_schema_length then your query might fail, and if the schema is not fully represented within these rows then the result might be incomplete: df = pl.DataFrame({'a': ['{}', '{"b": "c"}', '{"e": "d"}']}) # we only check first 2 rows to determine schema # so field e is not in the result df.with_columns( pl.col('a').str.json_decode(infer_schema_length=2) )  shape: (3, 1) ┌───────────┐ │ a │ │ --- │ │ struct[1] │ ╞═══════════╡ │ {null} │ │ {"c"} │ │ {null} │ └───────────┘  So you can just set infer_schema_length to None pushing polars to scan full data to determine the schema: df = pl.DataFrame({'a': ['{}', '{"b": "c"}', '{"e": "d"}']}) df.with_columns( pl.col('a').str.json_decode(infer_schema_length=None) )  shape: (3, 1) ┌─────────────┐ │ a │ │ --- │ │ struct[2] │ ╞═════════════╡ │ {null,null} │ │ {"c",null} │ │ {null,"d"} │ └─────────────┘
Question: Using Panadas 2.2.3, sqlite3.version 2.6.0 and python 3.12.5, I get an error "table ... already exits" when using to_sql with if_exists='append'. I just try to append some data from a Pandas df to a SQLite DB table. Using if_exists='replace' produces the same result. In order to make sure that the db connection is active and the columns match, I used some simple print statements in a first try block and the failing to.sql in a second try block. Also a "select statement" from the same table is used in the first block. The first block is executed without an exception and the second block throws the message 'table "groupedData" already exists': (See print('ERROR Try 2')) Source code: try: print(db_conn) print(table_grouped) data = [x.keys() for x in db_conn.cursor().execute(f'select * from {table_grouped};').fetchall()] print(data) except Error as e: print('ERROR Try 1') print(e) try: print(df_grouped.head(5)) df_grouped.to_sql(table_grouped, db_conn, if_exists='append', index=False) #if_exists : {‘fail’, ‘replace’, ‘append’} db_conn.commit() except Error as e: print('ERROR Try 2') print(e)  Output: <sqlite3.Connection object at 0x000001C0E7C0EB60> groupedData [['CustomerID', 'TotalSalesValue', 'SalesDate']] CustomerID TotalSalesValue SalesDate 0 12345 400.0 2020-02-01 1 12345 1050.0 2020-02-04 2 12345 10.0 2020-02-10 3 12345 200.0 2021-02-01 4 12345 50.0 2021-02-04 ERROR Try 2 table "groupedData" already exists 

Answer: We can see what is happening by logging the SQL statements made by Pandas. This minimal example: import sqlite3 from sqlite3 import Error import pandas as pd table_name = 'tbl' df = pd.DataFrame([(1,)], columns=['a']) with sqlite3.connect(':memory:') as conn: # Log all (successful) SQL statements. conn.set_trace_callback(print) # Create table with differently cased name. CREATE = """CREATE TABLE Tbl (a int)""" conn.execute(CREATE) print('*** Updating table ***') try: df.to_sql(table_name, conn, if_exists='append', index=False) conn.commit() except Error as e: print(e)  Produces this output: CREATE TABLE Tbl (a int) *** Updating table *** SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name='tbl'; table "tbl" already exists  So we can see that when Pandas checks for the table's existence, it uses the exact name that is passed to to_sql(), so the existing table is not found. However when Pandas attempts to create the table, SQLite will raise an error* if a table already exists with the same case-insensitive name, as we can see in the SQLite CLI: sqlite> CREATE TABLE T (a int); sqlite> CREATE TABLE t (a int); Parse error: table t already exists CREATE TABLE t (a int); ^--- error here  Arguably Pandas could check in a case-insensitive way, as described here, but equally it could be argued that it is the programmer's responsibility to use consistent names†.  * Pandas raises a ValueError if it detects that a table already exists, however the code is trapping an SQLite Error, so the exception isn't being raised by Pandas. † In fact, it seems that this issue has been raised before, and the Pandas developers elected to not make any changes.
Question: In Python, assert statement produces no code if command line optimization options -O or -OO are passed. Does it happen for typing.assert_never()? Is it safe to declare runtime assertions that will not be optimized out? Consider the case from typing import assert_never def func(item: int | str): match item: case int(): ... case str(): ... case _: assert_never(item)  Is it guaranteed that the default branch will work even in optimized mode?

Answer: No. assert_never() is only a normal function consisting of a raise statement, at least in CPython. It is not removed at runtime, as with other typing features.  It should be emphasized that assert_never() has nothing special: It just takes in an argument of type Never and never returns anything (i.e., it raises): def assert_never(arg: Never) -> Never: raise AssertionError("Expected code to be unreachable")  Its inclusion in the stdlib was simply due to the expectation that it would be commonly used. To quote the typing guides:  Because the assert_never() helper function is frequently useful, it is provided by the standard library as typing.assert_never starting in Python 3.11, and is also present in typing_extensions starting at version 4.1. — § assert_never() and Exhaustiveness Checking | Python typing guides  The same section also says:  However, it is also possible to define a similar function in your own code, for example if you want to customize the runtime error message.
Question: After updating google chrome this weekend, headless mode using Selenium python API is bringing up a blank window when running in windows. The identical code I had running on a Debian VM does not work any longer. Here is a code snippet:  chrome_options = Options() chrome_options.add_argument("--headless=new") #previously used --headless chrome_options.add_argument('--disable-gpu') chrome_options.add_argument('--no-sandbox') chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--disable-automation") chrome_options.add_argument("--disable-extensions") chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"]) driver = webdriver.Chrome(options=chrome_options)  To isolate the problem, I removed all fqdn dns blocks I had enforced for privacy including: ad.doubleclick.net, analytics.yahoo.com, google-analytics.com, googleadservices.com, plausible.io, stats.g.doubleclick.net

Answer: It's a new bug in Chrome / Chromedriver 129 when using the new headless mode on Windows: https://github.com/SeleniumHQ/selenium/issues/14514#issuecomment-2357777800. https://issues.chromium.org/issues/359921643#comment2 In the meantime, use --window-position=-2400,-2400 to hide the window. chrome_options.add_argument("--window-position=-2400,-2400")  Or use Chrome's older headless mode (while it still exists), until the fix is released: chrome_options.add_argument("--headless=old")
Question: Given a type t (originally comes from function annotations), I need to create a default value of that type. Normally, t() will do just that and work for many types, including basic types such as bool or int. However, tuple[bool, int]() returns an empty tuple, which is not a correct default value. It can get slightly trickier with more complex types such as tuple[bool, list[int]]. I saw that tuple[bool, int].__args__ returns (<class 'bool'>, <class 'int'>), which might be useful for writing a recursive function that implements this, but I'm still having trouble with this. Is there an existing function to do this and return the default value? If not, how would I write this code to work with all standard types? I'm using Python 3.11.

Answer: Since tuple is the only instantiable standard type where the default value should not be just an instantiation of the type with no argument, you can simply special-case it in a function that recursively creates a default value for a given type: from types import GenericAlias def get_default_value(typ): if isinstance(typ, GenericAlias) and issubclass(typ.__origin__, tuple): return typ(map(get_default_value, typ.__args__)) return typ()  so that: print(get_default_value(tuple[bool, list[int]]))  outputs: (False, [])  Demo: https://ideone.com/wlb6TL
Question: If I only use datetime.now() with timedelta to calculate deltas, is it safe to ignore time zones? For example, is there a case where if a start time is before daylight savings, and an end time is after, that I will get the wrong result if I don't use a time zone aware call to datetime.now()?

Answer: No, it is not safe. Do calculations in UTC if you want the actual time between timezone-aware values. Two values in the same timezone without UTC will give "wall time". For example, DST ends Nov 3, 2024 at 2am: # "pip install tzdata" for up-to-date Windows IANA database. import datetime as dt import zoneinfo as zi zone = zi.ZoneInfo('America/Los_Angeles') start = dt.datetime(2024, 11, 3, 0, 0 , 0, tzinfo=zone) end = dt.datetime(2024, 11, 3, 3, 0 , 0, tzinfo=zone) print(end - start) print(end.astimezone(dt.UTC) - start.astimezone(dt.UTC))  Output: 3:00:00 4:00:00  Even though both start and end times are time zone-aware, midnight to 3am appears to be 3 hours, but is actually 4 hours due to gaining an hour when DST ends. Be careful with timedelta as well: print(start + dt.timedelta(hours=24)) print((start.astimezone(dt.UTC) + dt.timedelta(hours=24)).astimezone(zone))  Output: 2024-11-04 00:00:00-08:00 2024-11-03 23:00:00-08:00  Adding 24 hours to a time zone-aware values doesn't account for the time shift. Do the calculation in UTC and display as the original TZ. Note the second time accounts for gaining an hour and represents exactly 24 hours later. The first time doesn't. You may want the first calculation if you want the same "wall time" but the next day, such as a recurring daily meeting at 9am...you may not want adding a day to shift the meeting time on DST transitions.
Question: I have a large csv dataset the looks like the following: id,x,y,z 34295,695.117,74.0177,70.6486 20915,800.784,98.5225,19.3014 30369,870.428,98.742,23.9953 48151,547.681,53.055,174.176 34026,1231.02,73.7678,203.404 34797,782.725,73.9831,218.592 15598,983.502,82.9373,314.081 34076,614.738,86.3301,171.316 20328,889.016,98.9201,13.3068 ...  If I consider each of these lines an element, I would like to have a data structure where I can easily divide space into x,y,z ranges (3-d blocks of space) and determine how many elements are within a given block. For instance if I divided into cubes of 100 x 100 x 100: counts[900][100][100] = 3  because id's 20915, 30369, and 20328 from the excerpt of the csv above are all within the range x = 800-900, y = 0-100, and z = 0-100. The brute force way to create something like this is to create a multi-level dictionary as follows: import numpy import pandas df = pandas.read_csv("test.csv") xs = numpy.linspace(0, 1300, 14, endpoint=True) ys = numpy.linspace(0, 1000, 11, endpoint=True) zs = numpy.linspace(0, 1000, 11, endpoint=True) c = {} for x_index, x in enumerate(xs[:-1]): c[xs[x_index + 1]] = {} for y_index, y in enumerate(ys[:-1]): c[xs[x_index + 1]][ys[y_index + 1]] = {} for z_index, z in enumerate(zs[:-1]): c[xs[x_index + 1]][ys[y_index + 1]][zs[z_index + 1]] = df[(df["x"] > xs[x_index]) & (df["x"] <= xs[x_index + 1]) & (df["y"] > ys[y_index]) & (df["y"] <= ys[y_index + 1]) & (df["z"] > zs[z_index]) & (df["z"] <= zs[z_index + 1])]["id"].count() if (c[xs[x_index + 1]][ys[y_index + 1]][zs[z_index + 1]] > 0): print("c[" + str(xs[x_index + 1]) + "][" + str(ys[y_index + 1]) + "][" + str(zs[z_index + 1]) + "] = " + str(c[xs[x_index + 1]][ys[y_index + 1]][zs[z_index + 1]]))  This gives the expected output of: c[600.0][100.0][200.0] = 1 c[700.0][100.0][100.0] = 1 c[700.0][100.0][200.0] = 1 c[800.0][100.0][300.0] = 1 c[900.0][100.0][100.0] = 3 c[1000.0][100.0][400.0] = 1 c[1300.0][100.0][300.0] = 1  But since the actual production CSV file is very large, it is quite slow. Any suggestions for how to make it fast and a little less clunky?

Answer: You could cut and value_counts: tmp = df[['x', 'y', 'z']] bins = np.arange(0, np.ceil(np.max(tmp)/100)*100, 100) tmp.apply(lambda s: pd.cut(s, bins, labels=bins[1:])).value_counts().to_dict()  Output: {(900.0, 100.0, 100.0): 3, (600.0, 100.0, 200.0): 1, (700.0, 100.0, 100.0): 1, (700.0, 100.0, 200.0): 1, (800.0, 100.0, 300.0): 1, (1000.0, 100.0, 400.0): 1}  Or round up to the nearest 100 before value_counts: (np.ceil(df[['x', 'y', 'z']].div(100)) .mul(100).astype(int) .value_counts(sort=False) .to_dict() )  Output: {(600, 100, 200): 1, (700, 100, 100): 1, (700, 100, 200): 1, (800, 100, 300): 1, (900, 100, 100): 3, (1000, 100, 400): 1, (1300, 100, 300): 1}
Question: I'm automating some data processing and creating jira tickets out of it. Pandas does have to_html or to_csv or even to_markdown. But jira supports only wiki markup for creating a table. e.g. <!-- wiki markup --> ||header1||header2||header3||\r\n|cell 11|cell 12|cell 13|\r\n|cell 21|cell 22|cell 23|  will create    header1 header2 header3     cell 11 cell 12 cell 13   cell 21 cell 22 cell 23    Is there anyway to convert pandas dataframe to wiki markup table to be used in Jira? I'm keeping df.iterrows as Last resort since iterating over dataframe is not a recommended solution as per answers in How can I iterate over rows in a Pandas DataFrame? Since my expected dataframe is small, iteration should be fine in my case. This question can be considered as more of a curiosity what can be done in case of larger dataframes.

Answer: Don't reinvent the wheel, tabulate supports a jira template: from tabulate import tabulate tabulate(df, headers='keys', tablefmt='jira', showindex=False)  Output: '|| header1 || header2 || header3 ||\n| cell 11 | cell 12 | cell 13 |\n| cell 21 | cell 22 | cell 23 |'  If you really want the \r\n line separator: tabulate(df, headers='keys', tablefmt='jira', showindex=False).replace('\n', '\r\n')
Question: Suppose I have one CSV file with dimension m×n means m rows and n columns. I want to reduce its dimension by replacing average value of corresponding sub matrix. Example 1: Given we have 6×6 matrix (CSV file): col1,col2,col3,col4,col5,col6 a1,b1,c1,d1,e1, f1 a2,b2,c2,d2,e2, f2 a3,b3,c3,d3,e3, f3 a4,b4,c4,d4,e4, f4 a5,b5,c5,d5,e5, f5 a6,b6,c6,d6,e6, f6  If we want 2×2 matrix, then resultant CSV file should be below: col1, col2 a', d' a", d"  Where a'=(a1+a2+a3+b1+b2+b3+c1+c2+c3)/9 a"=(a4+a5+a6+b4+b5+b6+c4+c5+c6)/9 d'=(d1+d2+d3+e1+e2+e3+f1+f2+f3)/9 d"=(d4+d5+d6+e4+e5+e6+f4+f5+f6)/9  Example:2 Given we have 5×6 matrix (CSV file): col1,col2,col3,col4,col5,col6 a1,b1,c1,d1,e1, f1 a2,b2,c2,d2,e2, f2 a3,b3,c3,d3,e3, f3 a4,b4,c4,d4,e4, f4 a5,b5,c5,d5,e5, f5  If we want 2×2 matrix, then resultant CSV file should be below: col1, col2 a', d' a", d"  Where a'=(a1+a2+a3+b1+b2+b3+c1+c2+c3)/9 a"=(a4+a5+b4+b5+c4+c5)/6 d'=(d1+d2+d3+e1+e2+e3+f1+f2+f3)/9 d"=(d4+d5+e4+e5+f4+f5)/6  Example 3: Given we have 6×5 matrix (CSV file): col1,col2,col3,col4,col5,col6 a1,b1,c1,d1,e1 a2,b2,c2,d2,e2 a3,b3,c3,d3,e3 a4,b4,c4,d4,e4 a5,b5,c5,d5,e5 a6,b6,c6,d6,e6  If we want 2×2 matrix, then resultant CSV file should be below: col1, col2 a', d' a", d"  Where a'=(a1+a2+a3+b1+b2+b3+c1+c2+c3)/9 a"=(a4+a5+a6+b4+b5+b6+c4+c5+c6)/9 d'=(d1+d2+d3+e1+e2+e3)/6 d"=(d4+d5+d6+e4+e5+e6)/6  I want the python code which can reduce the dimension by putting the average of the sum of all sub matrix. For in example1, we have given 6×6 matrix, we want 2×2 matrix, so we consider (6÷2) × (6÷2) = 3×3 sub matrix and calculate average of 9 elements of 3×3 matrix , which is the one element of resultant 2×2 matrix and so on. And in example2, if given dimension isn't multiple of resultant dimension, we use ceiling function, we first start by consider ceiling(5÷2)×(6÷2) = 3×3 matrix, and at the end(corner) , we mayn't get 3×3 matrix, we just calculate average of remaining elements, as we see in example2, example3.

Answer: Assuming this example:  col1 col2 col3 col4 col5 col6 0 0 1 2 3 4 5 1 6 7 8 9 10 11 2 12 13 14 15 16 17 3 18 19 20 21 22 23 4 24 25 26 27 28 29 5 30 31 32 33 34 35  You could rename the indexes (with set_axis), stack, and groupby.mean: import math n, m = 2, 2 # desired shape out = (df .set_axis(np.arange(df.shape[0])//math.ceil(df.shape[0]/n), axis=0) .set_axis(np.arange(df.shape[1])//math.ceil(df.shape[1]/m), axis=1) .stack().groupby(level=[0, 1]).mean().unstack() .rename(columns=lambda x: f'col{x+1}') # optional )  Alternative using numpy and padding (numpy.pad) with NaNs before reshape and nanmean: import math n, m = 2, 2 # desired shape out = pd.DataFrame(np.nanmean(np.pad(df.astype(float), [(0, df.shape[0]%n), (0, df.shape[1]%m)], constant_values=np.nan) .reshape(n, math.ceil(df.shape[0]/n), m, -1), axis=(1, 3) ) ).rename(columns=lambda x: f'col{x+1}')  Output:  col1 col2 0 7.0 10.0 1 25.0 28.0  Output with a 5x6 input (last row missing):  col1 col2 0 7.0 10.0 1 22.0 25.0
Question: I saw this example using QState, which seems to work with PyQt5. However on trying to use PySide for this, I get this error; Traceback (most recent call last): File ".../qstate-error.py", line 16, in <module> state1.assignProperty(widget, b'test', 1) TypeError: 'PySide2.QtCore.QState.assignProperty' called with wrong argument types: PySide2.QtCore.QState.assignProperty(QWidget, bytes, int) Supported signatures: PySide2.QtCore.QState.assignProperty(PySide2.QtCore.QObject, bytes, typing.Any)  Unfortunately I have to use PySide for work, so switching to PyQt5 is not an option. I have tried this with PySide2 and PySide6 - both are throwing this error. But then I saw this question that explicitly mentions PySide - so presumably it works. So do QState require some special setup that I am not aware of? Or is there something broken in my build? I'm working on Rocky 8, python 3.9.16 For reference, the following example works in pyqt5, but does not work in pyside2 (same result in pyside6). try: from PySide2.QtWidgets import QApplication, QWidget from PySide2.QtCore import QState except ImportError: from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtCore import QState if __name__ == '__main__': app = QApplication([]) widget = QWidget() state1 = QState() state1.assignProperty(widget, b'test', 1) widget.show() app.exec_() 

Answer: It's a bug in the signature definition, reported in PYSIDE-2444. Based on the PyQt/PySide convention, a C++ char should be a bytes in Python, in fact the function works as expected in PyQt5/6, and also the latest PySide6 releases (due to the fix related to the above bug). In reality, many functions that accept char also accept standard Python strings and are internally converted, meaning that you can just use the following: state1.assignProperty(widget, 'test', 1)  In case you need more strict typing and always use bytes, you can eventually use a QState subclass and "override" assignProperty() by eventually converting the type and calling the base implementation. It won't be a real override (as you could just create a function with any name and call the original assignProperty()), but it would be more consistent in code reading.
Question: I am working on a dataframe with the following structure: df = pl.DataFrame({ "datetime": [ "2024-09-24 00:00", "2024-09-24 01:020", "2024-09-24 02:00", "2024-09-24 03:00", ], "Bucket1": [2.5, 8, 0.7, 12], "Bucket2": [3.7, 10.1, 25.9, 9.9], "Bucket3": [40.0, 15.5, 10.7, 56], })  My goal is to output a table that counts the number of times a group of values appears across my dataset, something like this: shape: (4, 2) ┌───────────────────┬──────┐ │ datetime ┆ 0-10 │ │ --- ┆ --- │ │ str ┆ u32 │ ╞═══════════════════╪══════╡ │ 2024-09-24 00:00 ┆ 2 │ │ 2024-09-24 01:020 ┆ 1 │ │ 2024-09-24 02:00 ┆ 1 │ │ 2024-09-24 03:00 ┆ 1 │ └───────────────────┴──────┘  I have tried a couple approaches, like using pl.when together with .is_between to do something like when (Bucket1.is_between(0, 10, closed="left") | Bucket1.is_between(0, 10, closed="left")) then (1) But the result just evaluates to 1 regardless of how many Buckets evaluate to True. and also using concat list columns = ["Bucket1", "Bucket2", "Bucket3"] df.with_columns( pl.concat_list( [pl.col(col).is_between(0,10,closed="left") for col in columns] ) .arr.sum() .alias("0-10") )  The first approach didn't work as it just returns a list of booleans. The second one errors out with Invalid input for "col", Expected iterable of type "str" or "DataType", got iterable of "Expr" How could I tackle this problem using Polar?

Answer: In the latest version 1.8.1 of Polars, your code runs as expected after replacing the arr namespace with the list namespace. Moreover, it can be simplified to avoid the list comprehension as follows. cols = ["Bucket1", "Bucket2", "Bucket3"] df.with_columns( pl.concat_list(pl.col(cols).is_between(0, 10, closed="left")).list.sum().alias("0-10") )  shape: (4, 5) ┌───────────────────┬─────────┬─────────┬─────────┬──────┐ │ datetime ┆ Bucket1 ┆ Bucket2 ┆ Bucket3 ┆ 0-10 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ str ┆ f64 ┆ f64 ┆ f64 ┆ u32 │ ╞═══════════════════╪═════════╪═════════╪═════════╪══════╡ │ 2024-09-24 00:00 ┆ 2.5 ┆ 3.7 ┆ 40.0 ┆ 2 │ │ 2024-09-24 01:020 ┆ 8.0 ┆ 10.1 ┆ 15.5 ┆ 1 │ │ 2024-09-24 02:00 ┆ 0.7 ┆ 25.9 ┆ 10.7 ┆ 1 │ │ 2024-09-24 03:00 ┆ 12.0 ┆ 9.9 ┆ 56.0 ┆ 1 │ └───────────────────┴─────────┴─────────┴─────────┴──────┘