eshangj/stackoverflow_q_and_a_sample huggingface dataset:
Question: I'm trying to take the value from the last row of a df col and replace it with the first value. I'm returning a value error. import pandas as pd df = pd.DataFrame({'name': ['tom','jon','sam','jane','bob'], 'age': [24,25,18,26,17], 'Notification': [np.nan,'2025-01-03 14:19:35','2025-01-03 14:19:39','2025-01-03 14:19:41','2025-01-03 14:19:54'], 'sex':['male','male','male','female','male']}) df_test = df.copy() df_test['Notification'] = pd.to_datetime(df_test['Notification']) df_test['Notification'].iloc[0] = df_test['Notification'].tail(1)  Error: ValueError: Could not convert object to NumPy datetime 

Answer: You need to edit this line: df.loc[df.index[0], 'age'] = df.loc[df.index[-1], 'age']  Complete code: import pandas as pd import numpy as np df = pd.DataFrame({'name': ['tom', 'jon', 'sam', 'jane', 'bob'], 'age': [np.nan, 25, 18, 26, 17], 'sex': ['male', 'male', 'male', 'female', 'male']}) df.loc[df.index[0], 'age'] = df.loc[df.index[-1], 'age']  Cheers!!!
Question: While working on first-passage probabilities, I encountered this problem. I want to find a NumPythonic way (without explicit loops) to leave only the first occurrence of strictly increasing values in each row of a numpy array, while replacing repeated or non-increasing values with zeros. For instance, if arr = np.array([ [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5], [1, 1, 2, 2, 2, 3, 2, 2, 3, 3, 3, 4, 4], [3, 2, 1, 2, 1, 1, 2, 3, 4, 5, 4, 3, 2]])  I would like to get as output: out = np.array([ [1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 5, 0], [1, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 4, 0], [3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 0, 0]]) 

Answer: Maximum can be accumulated per-row: >>> arr array([[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5], [1, 1, 2, 2, 2, 3, 2, 2, 3, 3, 3, 4, 4], [3, 2, 1, 2, 1, 1, 2, 3, 4, 5, 4, 3, 2]]) >>> np.maximum.accumulate(arr, axis=1) array([[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5], [1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4], [3, 3, 3, 3, 3, 3, 3, 3, 4, 5, 5, 5, 5]])  Then you can easily mask out non-increasing values: >>> m_arr = np.maximum.accumulate(arr, axis=1) >>> np.where(np.diff(m_arr, axis=1, prepend=0), arr, 0) array([[1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 5, 0], [1, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 4, 0], [3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 0, 0]])
Question: Say I have a matrix like Matrix = [[A11, A12, A13, A14], [A21, A22, A23, A24], [A31, A32, A33, A34], [A41, A42, A43, A44]], and suppose I want to convert it to a block matrix [[A,B], [C,D]], where A = [[A11, A12], [A21, A22]] B = [[A13, A14], [A23, A24]] C = [[A31, A32], [A41, A42]] D = [[A33, A34], [A43, A44]]. What do I need to type to quickly extract the matrices A, B, C, and D?

Answer: Without using loops, you can reshape your array (and reorder the dimensions with moveaxis): A, B, C, D = np.moveaxis(Matrix.reshape((2,2,2,2)), 1, 2).reshape(-1, 2, 2)  Or: (A, B), (C, D) = np.moveaxis(Matrix.reshape((2,2,2,2)), 1, 2)  For a generic answer on an arbitrary shape: x, y = Matrix.shape (A, B), (C, D) = np.moveaxis(Matrix.reshape((2, x//2, 2, y//2)), 1, 2)  Output: # A array([['A11', 'A12'], ['A21', 'A22']], dtype='<U3') # B array([['A13', 'A14'], ['A23', 'A24']], dtype='<U3') # C array([['A31', 'A32'], ['A41', 'A42']], dtype='<U3') # D array([['A33', 'A34'], ['A43', 'A44']], dtype='<U3')
Question: Im trying to generate signed urls, so i followed the official guide but im getting this error: google.auth.exceptions.TransportError: Error calling sign_bytes: {'error': {'code': 403, 'message': "Permission 'iam.serviceAccounts.signBlob' denied on resource (or it may not exist).", 'status': 'PERMISSION_DENIED', 'details': [{'@type': 'type.googleapis.com/google.rpc.ErrorInfo', 'reason': 'IAM_PERMISSION_DENIED', 'domain': 'iam.googleapis.com', 'metadata': {'permission': 'iam.serviceAccounts.signBlob'}}]}}  I have done:  Enable the Service Account Credentials API   Create a Service Account and grant Storage Object User role and Service Account Token Creator role.   Set up authentication for Cloud Storage and Service account impersonation gcloud auth application-default login --impersonate-service-account=virtu-backend@hip-apricot-446418-f2.iam.gserviceaccount.com   Run the example code import datetime from google.cloud import storage storage_client = storage.Client() bucket = storage_client.bucket('virtu_users_uploaded_pictures') blob = bucket.blob('test1') url = blob.generate_signed_url( version="v4", # This URL is valid for 15 minutes expiration=datetime.timedelta(minutes=15), # Allow GET requests using this URL. method="GET", ) print("Generated GET signed URL:") print(url) print("You can use this URL with any user agent, for example:") print(f"curl '{url}'")    I also checked the assigned permissions of Service Account Token Creator  UPDATE: I tried a more minimal case: from google.cloud import storage storage_client = storage.Client() bucket = storage_client.bucket('virtu_users_uploaded_pictures') print(bucket.exists())  And i get this error: google.auth.exceptions.RefreshError: ('Unable to acquire impersonated credentials', '{\n "error": {\n "code": 403,\n "message": "Permission \'iam.serviceAccounts.getAccessToken\' denied on resource (or it may not exist).",\n "status": "PERMISSION_DENIED",\n "details": [\n {\n "@type": "type.googleapis.com/google.rpc.ErrorInfo",\n "reason": "IAM_PERMISSION_DENIED",\n "domain": "iam.googleapis.com",\n "metadata": {\n "permission": "iam.serviceAccounts.getAccessToken"\n }\n }\n ]\n }\n}\n')  The bucket exists and there is no typo error 

Answer: Per the documentation  Service Account Token Creator (roles/iam.serviceAccountTokenCreator): this role is required for generating short-lived credentials for a service account when a private key file is not provided locally. This role should be granted to the principal that will create the signed URL.  This means you have to assign the role to the user (person/human being) who is invoking the call to impersonate the service account from your CLI e.g. if you're the one doing it, then you have to assign it to yourself and not the service account.
Question: I'm testing basic GET requests under from the requests module. I'm using requests.Session to create a session instance which is later passed into the function. I'm mocking the the function calls. I've patched the session.get call within the function, however I get the following error:  AssertionError: get('https://example-name/v1/0001') call not found  I'm assuming I haven't patched the location correctly but I'm not sure where specifically I am going wrong. main.py import requests base_url = "https://example-name/v1/" session = requests.Session() def fetch_data_with_session(session, base_url, endpoint): response = session.get(base_url + endpoint) return response.json()  test_main.py import unittest from requests import Session from main import fetch_data_with_session from unittest.mock import Mock, patch class TestingGet(unittest.TestCase): @patch.object(Session, 'get') def test_fetch(self, mock_get): data = { "id": "0001", "name": "John Doe", } mock_response = Mock() mock_response.json.return_value = data mock_get.return_value = mock_response mock_session = Mock() retrieved_get_data = fetch_data_with_session(mock_session, "https://example-name/v1/", "0001") mock_get.assert_any_call("https://example-name/v1/0001") self.assertEqual = (outages_data, data) if __name__ == '__main__': unittest.main()  I also tried using the mock.get.assert_called_with and got a similar error. assert_called_with raise AssertionError(error_message) AssertionError: expected call not found. Expected: get('https://example-name/v1/0001') Actual: not called.  Again, I'm assuming I'm not patching the GET call correctly, but not sure how to correct it. EDIT: Thanks @Guy who provided the answer. The updated module code is below. Please see @Guy's answer for the explanation @patch.object(requests.Session, 'get') def test_fetch(self, mock_get): data = { "id": "exampleID", "name": "example", "devices": [ { "id": "0001", "name": "Device A" } ] } mock_response = Mock() mock_response.json.return_value = data mock_get.return_value = mock_response outages_data = fetch_data_with_session(mock_get, "https://example-name/v1/", "0001") mock_get.get.assert_called_with("https://example-name/v1/0001") self.assertEqual = (outages_data, data) 

Answer: There are two issues in you code  You use mock_session for the API call but test mock_get for it  mock_get and mock_session are the session.get function, not Session instance   Simplified test with two options @patch.object(Session, 'get') def test_fetch(self, mock_get): fetch_data_with_session(mock_get, "https://example-name/v1/", "0001") mock_get.assert_any_call("https://example-name/v1/0001") # or mock_session = Mock() fetch_data_with_session(mock_session, "https://example-name/v1/", "0001") mock_session.assert_any_call("https://example-name/v1/0001")  and main.py def fetch_data_with_session(get, base_url, endpoint): response = get(base_url + endpoint) return response.json()
Question: I have a line vector where each segment has a randomly assigned direction. The image shows an example (which consists of two connected lines),  How to set the direction of each line so that it is consistent? That is, connected lines have the same direction? The line vector does not have a direction attribute. This information is somehow stored in the GPKG file in some other way.

Answer: You can use line_merge. For it to work you need each cluster of connected lines to be a multiline, which can be done using GeoPandas: from shapely.wkt import loads import geopandas as gpd data = [[1, 'LineString (543125 6941963, 544217 6941907)'], [2, 'LineString (544957 6941417, 544217 6941907)'], [3, 'LineString (544957 6941417, 545151 6942222)'], [4, 'LineString (545882 6941574, 545854 6940094)'], [5, 'LineString (545531 6942647, 546567 6943424)'], [6, 'LineString (546548 6944201, 546567 6943424)'], [7, 'LineString (546548 6944201, 547297 6944248)']] geoms = [loads(x[1]) for x in data] df = gpd.GeoDataFrame(data=data, geometry=geoms, columns=["line_id", "wkt"], crs=3006) #Creata a column with buffered lines (polygon geometries) df["buffered"] = df.buffer(100) #Dissolve them into three polygons, each connetcting a number of lines poly_df = df.set_geometry("buffered").dissolve().explode() poly_df["poly_id"] = range(poly_df.shape[0]) #Give each polygon a unique id ax = poly_df.plot(column="poly_id", zorder=0, cmap="Pastel2") df.plot(column="line_id", ax=ax, cmap="tab10", zorder=1)   #Join the polygon id to the connected lines, # to give connected lines the same poly_id df["poly_id"] = df.sjoin(poly_df)["poly_id"] #Dissolve by polygon id to union connected lines into multilines, # line_merge to align the line directions, explode back to individual lines df = df.dissolve(by="poly_id").line_merge().explode()
Question: Q is a 3D matrix and could for example have the following shape:  (4000, 25, 25)  I want raise Q to the power n for {0, 1, ..., k} and sum it all. Basically, I want to calculate  \sum_{i=0}^{k-1}Q^n  I have the following function that works as expected: def sum_of_powers(Q: np.ndarray, k: int) -> np.ndarray: Qs = np.sum([ np.linalg.matrix_power(Q, n) for n in range(k) ], axis=0) return Qs  Is it possible to speed up my function or is there a faster method to obtain the same output?

Answer: We can perform this calculation in O(log k) matrix operations. Let M(k) represent the k'th power of the input, and S(k) represent the sum of those powers from 0 to k. Let I represent an appropriate identity matrix. Approach 1 If you expand the product, you'll find that (M(1) - I) * S(k) = M(k+1) - I. That means we can compute M(k+1) using a standard matrix power (which takes O(log k) matrix multiplications), and compute S(k) by using numpy.linalg.solve to solve the equation (M(1) - I) * S(k) = M(k+1) - I: import numpy.linalg def option1(Q, k): identity = numpy.eye(Q.shape[-1]) A = Q - identity B = numpy.linalg.matrix_power(Q, k+1) - identity return numpy.linalg.solve(A, B)  This does depend on M(1) - I being invertible, though. Fortunately, there's another approach that doesn't have that limitation. Approach 2 The standard exponentation by squaring algorithm computes M(2*k) as M(k)*M(k) and M(2*k+1) as M(2*k)*M(1). We can alter the algorithm to track both S(k-1) and M(k), by computing S(2*k-1) as S(k-1)*M(k) + S(k-1) and S(2*k) as S(2*k-1) + M(2*k): import numpy def option2(Q, k): identity = numpy.eye(Q.shape[-1]) if k == 0: res = numpy.empty_like(Q) res[:] = identity return res power = Q sum_of_powers = identity # Looping over a string might look dumb, but it's actually the most efficient option, # as well as the simplest. (It wouldn't be the bottleneck even if it wasn't efficient.) for bit in bin(k+1)[3:]: sum_of_powers = (sum_of_powers @ power) + sum_of_powers power = power @ power if bit == "1": sum_of_powers += power power = power @ Q return sum_of_powers
Question: #Column X contains the suffix of one of V* columns. Need to put set column V{X} to 9 if X > 1. #But my code created a new column 'VX' instead of updating one of the V* columns import pandas as pd df = pd.DataFrame({'EMPLID': [12, 13, 14, 15, 16, 17, 18], 'V1': [2,3,4,50,6,7,8], 'V2': [3,3,3,3,3,3,3], 'V3': [7,15,8,9,10,11,12], 'X': [2,3,1,3,3,1,2] }) # Expected output: # EMPLID V1 V2 V3 X # 12 2 9 7 2 # 13 3 3 9 3 # 14 4 3 8 1 # 15 50 3 9 3 # 16 6 3 9 3 # 17 7 3 11 1 # 18 8 9 12 2  My code created a new column 'VX' instead of updating one of the V* columns: df.loc[(df['X'] > 1), f"V{'X'}"] = 9  Any suggestion is appreciated. Thank you.

Answer: # your dataframe df = pd.DataFrame({'EMPLID': [12, 13, 14, 15, 16, 17, 18], 'V1': [2,3,4,50,6,7,8], 'V2': [3,3,3,3,3,3,3], 'V3': [7,15,8,9,10,11,12], 'X': [2,3,1,3,3,1,2] })  First, we get the columns names that we want to change and their indices on the original dataframe. # column name x = df['X'][(df['X'] > 1)] # column names mapped to your scenario columns = [f'V{v}' for v in x] # desired indexes positions = x.index.values #Then we convert the column names to indices and use these indices to update the positions matching the conditions. column_indices = [df.columns.get_loc(col) for col in columns]  Now, we can use two approaches here. A vectorized approach Convert the dataframe to numpy array, change the desired positions all at once and change the result back to a dataframe.  import numpy as np # the original dataframe column names column_names = df.columns # convert the dataframe to numpy (this will 'remove' the column names) df_array = df.values # put the columns back (axis=0 will stack the columns at the top of the array) df_array = np.concatenate([[column_names], df_array], axis=0) # position+1 because when using pandas to get the row index, we ignore the columns (which would have index 0) df_array[positions+1, column_indices] = 9 # convert the result back to a dataframe df = pd.DataFrame(df_array[1:], columns=column_names) print(df)  Output:  EMPLID V1 V2 V3 X 0 12 2 9 7 2 1 13 3 3 9 3 2 14 4 3 8 1 3 15 50 3 9 3 4 16 6 3 9 3 5 17 7 3 11 1 6 18 8 9 12 2  A loop approach The easiest way would be just to loop over the rows and columns, changing one value at a time. for row, column in zip(positions, column_indices): df.iat[row,column] = 9  If your dataframe is small, the vectorized approach won't have as much of an advantage over the for loop.
Question: I was creating a tkinter/turtle program similar to MS paint and I have the barebones Turtle finished but I am unsure of how to add the turtle into tkinter as a sort of window (Having the tkinter as the main application window with a window of python inside the window acting as a widget almost how a label or checkbox or check button would) how would I implement this given the code: import turtle import tkinter as tk from tkinter import ttk root = tk.Tk() root.title("") frame = tk.Frame(root, padx=20, pady=20) frame.pack(padx=10, pady=10) label = tk.Label(frame, text="", font=("Arial", 16, "bold")) label.pack() # Setup turtle t = turtle.Turtle() t.shape("square") t.fillcolor("") t.up() # Pen is up by default t.turtlesize(1000000, 1000000) # Hide the turtle border turtle.tracer(False) turtle.hideturtle() # Functions def draw(x, y): t.down() # Pen down to draw t.goto(x, y) turtle.update() def move(x, y): t.up() # Pen up to move without drawing t.goto(x, y) turtle.update() # Bind events t.ondrag(draw) # Draw when dragging turtle.onscreenclick(move) # Move when clicking # Press Space to Save turtle.listen() turtle.done() root.mainloop() 

Answer: You can use the RawTurtle() function to define your turtle then from there you can use ScrolledCanvas() function and TurtleScreen to create the screen and then it goes into tkinter, then you can use screen instead of updating turtle itself. from turtle import RawTurtle, TurtleScreen, ScrolledCanvas import tkinter as tk from tkinter import ttk root = tk.Tk() root.title("Drawing in turtle with TKinter") # Create a ScrolledCanvas for the turtle canvas = ScrolledCanvas(root, width=800, height=600) # Canvas with scrollbars canvas.pack(fill=tk.BOTH, expand=True) # Make the canvas expand to fill the window # Create a TurtleScreen from the canvas screen = TurtleScreen(canvas) # TurtleScreen is needed for turtle graphics screen.bgcolor("white") # Set the background color of the screen frame = tk.Frame(root, padx=20, pady=20) frame.pack(padx=10, pady=10) label = tk.Label(frame, text="", font=("Arial", 16, "bold")) label.pack() # Setup turtle t = RawTurtle(screen) # Create a turtle object t.shape("square") # Set the shape of the turtle t.fillcolor("") # No fill color for the turtle t.pencolor("black") # Set the default pen color t.pensize(10) t.up() # Pen is up by default (no drawing when moving) t.turtlesize(1000000, 1000000) # Hide the turtle border by making it extremely large screen.tracer(False) # Disable automatic screen updates for smoother drawing # Functions def draw(x, y): t.down() # Pen down to draw t.goto(x, y) screen.update() def move(x, y): t.up() # Pen up to move without drawing t.goto(x, y) screen.update() # Bind events t.ondrag(draw) # Draw when dragging screen.onscreenclick(move) # Move when clicking # Press Space to Save root.mainloop()
Question: I have this python code. But why does it print "NEW" in the first case, and "Status.NEW" in the second case? import enum class Status(str, enum.Enum): """Status options.""" NEW = "NEW" EXCLUDED = "EXCLUDED" print("" + Status.NEW) print(Status.NEW) 

Answer: This is a quirk of multiple inheritance (one of the reasons why a lot of people choose to shun it). print("" + Status.NEW)  Here you're using the + operator on your Status.NEW object. Since Status inherits from str, it inherits the __add__ method from there. str.__add__ does string concatenation and uses its raw string value. print(Status.NEW)  Here there's no string concatenation, but print calls the __str__ method on any object you pass to it. Your Status.NEW object inherits its __str__ method from the Enum side of the family, so in that context it gets printed as an Enum value instead of a string. If you're in Python 3.11 or greater, you can do this: import enum class Status(enum.StrEnum): """Status options.""" NEW = "NEW" EXCLUDED = "EXCLUDED" print("" + Status.NEW) print(Status.NEW)  That avoids ambiguity about whether your objects are treated as Enums or strings. Otherwise, you just have to be careful.
Question: Trying to extract the vendor information (Apple, Samsung, etc) from Probe Request coming from mobile, So far no luck. Not sure where the corrections to be made to get this info. Adding my code: import codecs from scapy.all import * from netaddr import * def handler(p): if not (p.haslayer(Dot11ProbeResp) or p.haslayer(Dot11ProbeReq) or p.haslayer(Dot11Beacon)): return rssi = p[RadioTap].dBm_AntSignal dst_mac = p[Dot11].addr1 src_mac = p[Dot11].addr2 ap_mac = p[Dot11].addr2 global macf maco = EUI(src_mac) try: macf = maco.oui.registration().org except NotRegisteredError: macf = "Not available" info = f"rssi={rssi:2}dBm, dst={dst_mac}, src={src_mac}, ap={ap_mac}, manf= {macf}" if p.haslayer(Dot11ProbeReq): stats = p[Dot11ProbeReq].network_stats() ssid = str(stats['ssid']) channel = None if "channel" in stats: channel = stats['channel'] print(f"[ProbReq ] {info}") print(f"ssid = {ssid}, channel ={channel}") #rate= {rates} sniff(iface="wlan1", prn=handler, store=0) 

Answer: There are a few things that should be taken into consideration when dealing with your problem. First, the OUI used by the netaddr 1.3.0 package is outdated. I have an iPhone 16 with OUI 0C-85-E1. You can check directly in IEEE or here that it is a valid OUI, but it's not updated in the netaddr source. You can solve this problem using another approach to get OUI info from the web. oui = src_mac[:8].upper().replace(":", "-") try: response = requests.get(f"https://api.macvendors.com/{oui}") if response.status_code == 200: macf = response.text else: macf = "Not available" except Exception as e: macf = "Not available"  But here there's the second problem. Apple uses a private Wi-Fi addresses security functionality that prevents from showing the real OUI on all requests, including probe requests. Check here when this option is off:  And when it's on:  You can check that OUI 6E-BA-4F it's invalid. Android has a similar function too. So you will have the same problem. If your clients use this function there is no way to determine the vendor based on OUI from probe requests.
Question: I'm calculating EWMA values for array of streamflow, and code is like below: import polars as pl import numpy as np streamflow_data = np.arange(0, 20, 1) adaptive_alphas = np.concatenate([np.repeat(0.3, 10), np.repeat(0.6, 10)]) streamflow_series = pl.Series(streamflow_data) ewma_data = np.zeros_like(streamflow_data) for i in range(1, len(streamflow_series)): current_alpha = adaptive_alphas[i] ewma_data[i] = streamflow_series[:i+1].ewm_mean(alpha=current_alpha)[-1]  # When set dtype of ewma_data to float when initial it, output is like this Output: [0 0.58823529 1.23287671 1.93051717 2.67678771 3.46668163, 4.29488309 5.1560635 6.04512113 6.95735309 9.33379473 10.33353466, 11.33342058 12.33337091 13.33334944 14.33334021 15.33333625 16.33333457, 17.33333386 18.33333355] # When I don't point dtype of ewma_data and dtype of streamflow_data is int, output will be floored Output: [0 0 1 1 2 3 4 5 6 6 9 10 11 12 13 14 15 16 17 18]  But when length of streamflow_data is very big (such as >100000), this code will become very slow. So how can I extinguish for in my code and don't influence its result? Hope for your reply.

Answer: If you have only few alpha values and/or have some condition on which alpha should be used for which row, you could use pl.coalesce(), pl.when() and pl.Expr.ewm_mean(): df = pl.DataFrame({ "adaptive_alpha": np.concatenate([np.repeat(0.3, 10), np.repeat(0.6, 10)]), "streamflow": np.arange(0, 20, 1) }) df.with_columns( pl.coalesce( pl.when(pl.col.adaptive_alpha == alpha) .then(pl.col.streamflow.ewm_mean(alpha = alpha)) for alpha in df["adaptive_alpha"].unique() ).alias("ewma") ).with_columns(ewma_int = pl.col.ewma.cast(pl.Int32))  shape: (20, 4) ┌────────────────┬────────────┬───────────┬──────────┐ │ adaptive_alpha ┆ streamflow ┆ ewma ┆ ewma_int │ │ --- ┆ --- ┆ --- ┆ --- │ │ f64 ┆ i64 ┆ f64 ┆ i32 │ ╞════════════════╪════════════╪═══════════╪══════════╡ │ 0.3 ┆ 0 ┆ 0.0 ┆ 0 │ │ 0.3 ┆ 1 ┆ 0.588235 ┆ 0 │ │ 0.3 ┆ 2 ┆ 1.232877 ┆ 1 │ │ 0.3 ┆ 3 ┆ 1.930517 ┆ 1 │ │ 0.3 ┆ 4 ┆ 2.676788 ┆ 2 │ │ … ┆ … ┆ … ┆ … │ │ 0.6 ┆ 15 ┆ 14.33334 ┆ 14 │ │ 0.6 ┆ 16 ┆ 15.333336 ┆ 15 │ │ 0.6 ┆ 17 ┆ 16.333335 ┆ 16 │ │ 0.6 ┆ 18 ┆ 17.333334 ┆ 17 │ │ 0.6 ┆ 19 ┆ 18.333334 ┆ 18 │ └────────────────┴────────────┴───────────┴──────────┘