eshangj/stackoverflow_q_and_a_sample huggingface dataset:
Question: I am confused about different results of boolean indexing when using ~ after != versus when using just == I have a pandas df with 4 columns: dic = { "a": [1,1,1,0,0,1,1], "b": [0,0,1,1,0,0,0], "c": [1,0,1,0,0,1,0], "d": [0,0,1,0,0,1,0], } df = pd.DataFrame(data=dic) print(df) a b c d 0 1 0 1 0 1 1 0 0 0 2 1 1 1 1 3 0 1 0 0 4 0 0 0 0 5 1 0 1 1 6 1 0 0 0  I want to subset the whole df dataframe: I want to remove all rows which have all elements zero, but just on the columns b c d, and not on a. If I use ~ (not) operator after == I get the desired result: names = ["b","c","d"] df_A = df.loc[~(df[names] == 0.0).all(axis=1)] print(df_A) a b c d 0 1 0 1 0 2 1 1 1 1 3 0 1 0 0 5 1 0 1 1  But when I use just == I get different result: names = ["b","c","d"] df_B = df.loc[(df[names] != 0.0).all(axis=1)] print(df_B) a b c d 2 1 1 1 1  Do you have any idea why is this the case? Should these two not be the same? Thank you.

Answer: You're not correctly following De Morgan's law.  not (A or B) = (not A) and (not B) not (A and B) = (not A) or (not B)  If you use the opposite condition as input, you have to replace all (AND) by any (OR): df_B = df.loc[(df[names] != 0.0).any(axis=1)]  In English this would be:  I want to REMOVE (~) rows for which ALL values are 0 (== 0) I want to KEEP rows for which ANY value is NOT 0 (!= 0)  In short the rules for inverting input conditions are:  swapping strict/equal operators (e.g. >= becomes < ; > becomes <=) AND become OR and OR becomes AND adding/removing NOT (~)  Output:  a b c d 0 1 0 1 0 2 1 1 1 1 3 0 1 0 0 5 1 0 1 1
Question: I would like to have a function that accept list of conditions as parameter and filter given dataframe by all of them. Pseudocode should look like this: def Filter(df, conditions = ["a","b"]): conditions_dict = { "a": pl.col("x") < 5, "b": pl.col("x") > -3, "c": pl.col("z") < 7 } return df.with_columns( pl.when( any [conditions_dict[c] for c in conditions]) .then(pl.lit(False)) .otherwise(pl.lit(True)) .alias("bool") )  How to do it?

Answer: In general, you can create a generator for the filter expressions of interest and pass it to pl.any_horizontal to check whether any of them evaluates to True. This can be used within a pl.when().then().otherwise() construct. def custom_filter(df: pl.DataFrame, conditions: list[str]) -> pl.DataFrame: conditions_dict = { "a": pl.col("x") < 5, "b": pl.col("x") > -3, "c": pl.col("z") < 7 } return df.with_columns( pl.when( pl.any_horizontal(*(conditions_dict[c] for c in conditions)) ).then( pl.lit(False) ).otherwise( pl.lit(True) ).alias("bool") ) custom_filter(df, ["a"])  Note. In the specific example provided, you can simplify and drop the pl.when().then().otherwise() construct and replace it with the negation of the horizontal aggregation ~pl.any_horizontal(...). shape: (6, 3) ┌─────┬─────┬───────┐ │ x ┆ z ┆ bool │ │ --- ┆ --- ┆ --- │ │ i64 ┆ i64 ┆ bool │ ╞═════╪═════╪═══════╡ │ -4 ┆ -2 ┆ false │ │ -2 ┆ -3 ┆ false │ │ 1 ┆ 8 ┆ false │ │ 3 ┆ 4 ┆ false │ │ 5 ┆ 2 ┆ true │ │ 7 ┆ 1 ┆ true │ └─────┴─────┴───────┘
Question: I am trying to construct a hierarchy of dataclasses from a tuple, like so: from dataclasses import astuple, dataclass @dataclass class Child: name: str @dataclass class Parent: child: Child # this is what I want p = Parent(Child("Tim")) print(p) # this is what I get t = astuple(p) print(Parent(*t))  However, while this constructs a Parent as expected, its child is not of type Child: Parent(child=Child(name='Tim')) Parent(child=('Tim',))  Is there a way to construct Parent and child from the tuple t, or by some other means?

Answer: You can use a helper function that constructs an instance of a dataclass from a given tuple of values and recursively constructs child instances for fields that are dataclasses: from dataclasses import astuple, dataclass, fields, is_dataclass @dataclass class Child: name: str @dataclass class Parent: child: Child def from_tuple(cls, data): return cls(*( from_tuple(type, obj) if is_dataclass(type := field.type) else obj for obj, field in zip(data, fields(cls)) )) p = Parent(Child("Tim")) t = astuple(p) print(from_tuple(Parent, t))  This outputs: Parent(child=Child(name='Tim'))  Demo here
Question: I have multiple Nx3 points, and I sequentially generate a new value for each from its corresponding multivariate Gaussian, each with 1x3 mean and 3x3 cov. So, together, I have arrays: Nx3 array of points, Nx3 array of means and Nx3x3 array of covs. I only see how to do it with the classic for-loop: import numpy as np from scipy.stats import multivariate_normal # Generate example data N = 5 # Small number for minimal example, can be increased for real use case points = np.random.rand(N, 3) means = np.random.rand(N, 3) covs = np.array([np.eye(3) for _ in range(N)]) # Identity matrices as example covariances # Initialize an array to store the PDF values pdf_values = np.zeros(N) # Loop over each point, mean, and covariance matrix for i in range(N): pdf_values[i] = multivariate_normal.pdf(points[i], mean=means[i], cov=covs[i]) print("Points:\n", points) print("Means:\n", means) print("Covariances:\n", covs) print("PDF Values:\n", pdf_values)  Is there any way to speed this up? I tried to pass all directly to multivariate_normal.pdf, but also from the docs that does not seem to be supported (unlike a simpler case of generating values for Nx3 points, but with same mean and covariance. Maybe some implementation not from scipy? I might be too hopeful, but somehow I hope there is a simpler way to speed this up and avoid iterating with this for-loop in a big array of data directly with Pythonic loop.

Answer: Here is a solution involving Cholesky decomposition. method 1: import numpy as np x = points - means p = x.shape[1] res = np.exp(-0.5*(x*np.linalg.solve(covs, x)).sum(1)) res/(2*np.pi)**(p/2)/np.linalg.det(covs)**0.5 array([0.03356053, 0.03167125, 0.08042358, 0.04351325, 0.1328082 ])   method 2: y = np.linalg.solve(LU := np.linalg.cholesky(covs), points - means) np.exp(-0.5* (y**2+np.log(2*np.pi)).sum(1))/np.einsum('kii->ki',LU).prod(1) array([0.03356053, 0.03167125, 0.08042358, 0.04351325, 0.1328082 ])  method 3: multivariate_normal.pdf(y, np.zeros(p))/np.einsum('kii->ki',LU).prod(1) array([0.03356053, 0.03167125, 0.08042358, 0.04351325, 0.1328082 ])   method 4: Using svd U,S,V = np.linalg.svd(covs) res_svd = np.einsum('ki,kij,kj,kjl,kl->k', x, U, 1/S, V, x) np.exp(-0.5 * (res_svd + np.log(2 * np.pi * S).sum(1))) array([0.03356053, 0.03167125, 0.08042358, 0.04351325, 0.1328082 ])  Generate Data: import numpy as np from scipy.stats import multivariate_normal, wishart # Generate example data N = 5 # Small number for minimal example, can be increased for real use case p = 3 # dimension np.random.seed(10) points = np.random.rand(N, p) means = np.random.rand(N, p) covs = wishart.rvs(p, np.eye(p),N, 2) # Identity matrices as example covariances # Initialize an array to store the PDF values pdf_values = np.zeros(N) # Loop over each point, mean, and covariance matrix for i in range(N): pdf_values[i] = multivariate_normal.pdf(points[i], mean=means[i], cov=covs[i]) pdf_values array([0.03356053, 0.03167125, 0.08042358, 0.04351325, 0.1328082 ])
Question: Apologies for the possibly not-very-useful title; I couldn't figure out how to summarise this problem into one sentence. I'm trying to count how many "units" long a word is in Python 3.10. One "unit" is (C for consonant and V for vowel) either CV or VC or C or V (the latter two only being used when no pair can be made). For example, "piece" would be three units (pi-e-ce or pi-ec-e), "queue" would be four units (qu-e-u-e), and "lampshade" would be six units (la-m-p-s-ha-de). What I'm struggling with is how exactly I would detect these units without iterating through every combination of every vowel and consonant for each pair of letters. It'd be hugely inefficient to do this with iteration, but I can't think of anything better with my current knowledge of Python. What would be an efficient way of solving this problem? As an extra (optional) problem, what if digraphs are introduced, like "gh" and "th"? This would make words like "thigh" (four units, t-hi-g-h) into only two units (thi-gh), but also complicates the working-out.

Answer: Just convert everything to 'V' or 'C', respectively, in a list. Then check the first 2 letters in a loop. If it's 'CV' or 'VC' pop the list, and no matter what, pop it again. Count the iterations it takes to exhaust the list. def units(word) -> int: word = ["V" if c in "aeiou" else "C" for c in word] cnt = 0 while word: if ''.join(word[:2]) in ('CV', 'VC'): word.pop(0) word.pop(0) cnt += 1 return cnt for word in ('queue', 'piece', 'lampshade'): print(word, units(word))
Question: I have a Polars dataframe that contains multiple questions and answers. The problem is that each answer is contained in its own column, which means that I have a lot of redundant information. Therefore, I would like to have only one column for the questions and another for the answers. Here is an example of the data: data = { "ID" : [1,1,1], "Question" : ["A","B","C"], "Answer A" : ["Answer A", "Answer A", "Answer A"], "Answer B" : ["Answer B", "Answer B", "Answer B"], "Answer C" : ["Answer C", "Answer C", "Answer C"] } df = pl.DataFrame(data) df  My approach is to create other filter dataframes and then concact them, however i would like a fancier approach to this problem My current approach: A_df = ( df .drop(["Answer B","Answer C"]) .filter(pl.col("Question") == "A") .rename({"Answer A" : "Answer"}) ) B_df = ( df .drop(["Answer A","Answer C"]) .filter(pl.col("Question") == "B") .rename({"Answer B" : "Answer"}) ) C_df = ( df .drop(["Answer A","Answer B"]) .filter(pl.col("Question") == "C") .rename({"Answer C" : "Answer"}) ) df_final = pl.concat([A_df,B_df,C_df]) 

Answer: TLDR. import polars.selectors as cs ( df .unpivot( on=cs.starts_with("Answer"), index=["ID", "Question"], variable_name="Source", value_name="Answer", ) .filter( pl.col("Question") == pl.col("Source").str.strip_prefix("Answer ") ) .drop("Source") )  shape: (3, 3) ┌─────┬──────────┬───────────────────┐ │ ID ┆ Question ┆ Answer │ │ --- ┆ --- ┆ --- │ │ i64 ┆ str ┆ str │ ╞═════╪══════════╪═══════════════════╡ │ 1 ┆ A ┆ Some answer │ │ 1 ┆ B ┆ Some other answer │ │ 1 ┆ C ┆ Another answer │ └─────┴──────────┴───────────────────┘  Explanation. An approach that is a bit more general is to melt (pl.DataFrame.unpivot) the dataframe on the answer columns. This gives you a long format dataframe, which for each original row contains one row for each answer column. import polars.selectors as cs ( df .unpivot( on=cs.starts_with("Answer"), index=["ID", "Question"], variable_name="Source", value_name="Answer", ) )  shape: (9, 4) ┌─────┬──────────┬──────────┬───────────────────┐ │ ID ┆ Question ┆ Source ┆ Answer │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ str ┆ str ┆ str │ ╞═════╪══════════╪══════════╪═══════════════════╡ │ 1 ┆ A ┆ Answer A ┆ Some answer │ │ 1 ┆ B ┆ Answer A ┆ Some answer │ │ 1 ┆ C ┆ Answer A ┆ Some answer │ │ 1 ┆ A ┆ Answer B ┆ Some other answer │ │ 1 ┆ B ┆ Answer B ┆ Some other answer │ │ 1 ┆ C ┆ Answer B ┆ Some other answer │ │ 1 ┆ A ┆ Answer C ┆ Another answer │ │ 1 ┆ B ┆ Answer C ┆ Another answer │ │ 1 ┆ C ┆ Answer C ┆ Another answer │ └─────┴──────────┴──────────┴───────────────────┘  From here, it is easy to (after some transformation) filter for rows in which the Source column matches the question (see TLDR).
Question: I'm trying to compute a gradient on a map using np.gradient, but I'm encountering issues. To simplify my problem I am trying on an analytical function z = f(x,y) = -(x - 2)**2 - (y - 2)**2  np.gradient is not providing the expected results; the vectors should point towards the center. What am I doing wrong? Here is the code that I am running: import numpy as np import matplotlib.pyplot as plt # Define the grids for x and y x = np.linspace(0, 4, 100) # 100 points between 0 and 4 y = np.linspace(0, 4, 100) # 100 points between 0 and 4 X, Y = np.meshgrid(x, y) # Create a 2D grid # Define the function f(x, y) Z = -(X - 2)**2 - (Y - 2)**2 # Compute gradients numerically dz_dx, dz_dy = np.gradient(Z, x, y) # Downsampling to reduce the density of arrows step = 10 plt.figure(figsize=(10, 8)) contour = plt.contourf(X, Y, Z, cmap='viridis', levels=50, alpha=0.8) plt.colorbar(contour, label='f(x, y)') plt.quiver(X[::step, ::step], Y[::step, ::step], dz_dx[::step, ::step], dz_dy[::step, ::step], color='r', headlength=3, headwidth=4) plt.title('Function $f(x, y) = -(x - 2)^2 - (y - 2)^2$ and its gradients (numerical)') plt.xlabel('x') plt.ylabel('y') plt.grid(True) plt.show() 

Answer: the problem is not in the gradient function, it is in the different indexing order of np.meshgrid and np.gradient. by default np.gradient assumes the indexing is the same order as the arguments, ie: Z[x,y] -> np.gradient(Z, x, y)  whereas np.meshgrid default indexing results in the opposite indexing, Z[y,x] -> np.meshgrid(x,y) # default indexing = 'xy'  you didn't notice this bug because both X and Y are identical, if you made X and Y have different number of points you would get an error. I like to use Z[y,x], so just swap the order of arguments and return of np.gradient and you will get the correct result. dz_dy, dz_dx = np.gradient(Z, y, x) # Z[y,x]
Question: I want to compare lists from columns set_1 and set_2 in df_2 with ins column in df_1 to find all common elements. I've started doing it for one row and one column but I have no idea how to compare all rows between two dfs to get the desired result. Here is my code comparing set_1 and ins in the first row: import pandas as pd d1 = {'chr': [1, 1], 'start': [64, 1000], 'end': [150, 2000], 'family': ['a', 'b'], 'ins': [['P1-12', 'P1-22', 'P1-25', 'P1-28', 'P1-90'], ['P1-6', 'P1-89', 'P1-92', 'P1-93']]} df1 = pd.DataFrame.from_dict(data=d1) d2 = {'set_1': [['P1-12', 'P1-25', 'P1-28'], ['P1-6', 'P1-89', 'P1-93']], 'set_2': [['P1-89', 'P1-92', 'P1-93'], ['P1-25', 'P1-28', 'P1-90']]} df2 = pd.DataFrame.from_dict(data=d2) matches = [x for x in df2.iloc[0, 0] if x in df1.iloc[0, 4]]  There is a tiny part of my input data (in original input, df1 contains ~13k rows and df2 ~90): df1:  chr start end family ins 0 1 64 150 a [P1-12, P1-22, P1-25, P1-28, P1-90] 1 1 1000 2000 b [P1-6, P1-89, P1-92, P1-93]  df2:  set_1 set_2 0 [P1-12, P1-25, P1-28] [P1-89, P1-92, P1-93] 1 [P1-6, P1-89, P1-93] [P1-25, P1-28, P1-90]  The desired output should look like this:  chr start end family df2_index ins_set1 ins_set2 0 1 64 150 a 0 [P1-12, P1-25, P1-28] [] 1 1 64 150 a 1 [] [P1-25, P1-28, P1-90] 2 1 1000 2000 b 0 [] [P1-89, P1-92, P1-93] 3 1 1000 2000 b 1 [P1-6, P1-89, P1-93] [] 

Answer: Since you have objects you'll need to loop. I would first perform a cross-merge, then use a set for efficiency: out = df1.merge(df2, how='cross') cols = list(df2) ins = out.pop('ins').apply(set) for c in cols: out[c] = [[x for x in lst if x in ref] for ref, lst in zip(ins, out[c])]  Variant that should be a bit more efficient: out = df1.assign(ins=df1['ins'].apply(set)).merge(df2, how='cross') cols = list(df2) ins = out.pop('ins') for c in cols: out[c] = [[x for x in lst if x in ref] for ref, lst in zip(ins, out[c])]  Output:  chr start end family set_1 set_2 0 1 64 150 a [P1-12, P1-25, P1-28] [] 1 1 64 150 a [] [P1-25, P1-28, P1-90] 2 1 1000 2000 b [] [P1-89, P1-92, P1-93] 3 1 1000 2000 b [P1-6, P1-89, P1-93] []
Question: I have a water pipe network where each node is a house and each edge is a pipe connecting houses. The edges have a water volume as an attribute. I want to calculate the total volume reaching node 13. It should sum to 5+2+1+6+0+3+14+4+12+5+8+10+6+9=85  I've tried something like this, but it will repeat already traversed paths. For example I dont want to sum the value from node 1 to 2 (the weight 5) more than once: import networkx as nx from itertools import combinations G = nx.DiGraph() edges = [(1,2,5), (2,5,0), (3,2,2), (3,4,1), (4,5,6), (5,6,3), (7,8,14), (8,6,4), (6,9,12), (9,11,8), (10,9,5),(10,12,10), (11,12,6),(12,13,9)] for edge in edges: n1, n2, weight = edge G.add_edge(n1, n2, volume=weight) for n1, n2 in combinations(G.nodes,2): paths = list(nx.all_simple_paths(G=G, source=n1, target=n2)) for path in paths: total_weight = nx.path_weight(G=G, path=path, weight="volume") print(f"From node {n1} to {n2}, there's the path {'-'.join([str(x) for x in path])} \n with the total volume: {total_weight}") # From node 1 to 2, there's the path 1-2 # with the total volume: 5 # From node 1 to 5, there's the path 1-2-5 # with the total volume: 5 # From node 1 to 6, there's the path 1-2-5-6 # ... 

Answer: IIUC, just get a subgraph of all ancestors of 13 (and including 13), then compute the weighted size (= sum of all edge weights): target = 13 G.subgraph(nx.ancestors(G, target)|{target}).size(weight='volume')  Output: 85
Question: I'm trying to get the column name containing the maximum date value in my Polars DataFrame. I found a similar question that was already answered here. However, in my case, I have many columns, and adding them manually would be tedious. I would like to use column selectors cs.datetime() and have tried the following: import polars as pl from datetime import datetime import polars.selectors as cs data = { "ID" : [1,2,3], "Values_A" : [datetime(1,1,2),datetime(1,1,3),datetime(1,1,4)], "Values_B" : [datetime(1,1,4),datetime(1,1,7),datetime(1,1,2)] } df = pl.DataFrame(data) def arg_max_horizontal(*columns: pl.Expr) -> pl.Expr: return ( pl.concat_list(columns) .list.arg_max() .replace_strict({i: col_name for i, col_name in enumerate(columns)}) ) ( df .with_columns( Largest=arg_max_horizontal(pl.select(cs.datetime())) ) ) 

Answer: You were one step away and simply needed to select the column names of interest using df.select(cs.datetime()).columns. Then, we can unpack the list in the function call. Note. I've adapted the type hint of arg_max_horizontal accordingly. Moreover, (thanks to @Cameron Riddell), we can simplify conversion to a string representation using a cast to a suitable pl.Enum. def arg_max_horizontal(*columns: str) -> pl.Expr: return ( pl.concat_list(columns) .list.arg_max() .cast(pl.Enum(columns)) ) ( df .with_columns( Largest=arg_max_horizontal(*df.select(cs.datetime()).columns) ) )  shape: (3, 4) ┌─────┬─────────────────────┬─────────────────────┬──────────┐ │ ID ┆ Values_A ┆ Values_B ┆ Largest │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ datetime[μs] ┆ datetime[μs] ┆ str │ ╞═════╪═════════════════════╪═════════════════════╪══════════╡ │ 1 ┆ 0001-01-02 00:00:00 ┆ 0001-01-04 00:00:00 ┆ Values_B │ │ 2 ┆ 0001-01-03 00:00:00 ┆ 0001-01-07 00:00:00 ┆ Values_B │ │ 3 ┆ 0001-01-04 00:00:00 ┆ 0001-01-02 00:00:00 ┆ Values_A │ └─────┴─────────────────────┴─────────────────────┴──────────┘
Question: I have this DataFrame: Node Interface Speed Band_In carrier Date Server1 wan1 100 80 ATT 2024-05-09 Server1 wan1 100 50 Sprint 2024-06-21 Server1 wan1 100 30 Verizon 2024-07-01 Server2 wan1 100 90 ATT 2024-05-01 Server2 wan1 100 88 Sprint 2024-06-02 Server2 wan1 100 22 Verizon 2024-07-19  I need to convert Date field to this format 1-May, 2-Jun, 19-July, place them on each column in descending order. In this to look like this:  Node Interface Speed Band_In carrier 1-July 9-May 21-Jun Server1 wan1 100 80 ATT 80 50 30  I tried this: df['Date'] = pd.to_datetime(df['Date']).dt.strftime('%d-%b') df['is'] = df['Band_In'] / df['Speed'] * 100 df = df.pivot_table(index=['Node', 'Interface', 'carrier'], columns='Date', values='is').reset_index()  I need Date values in the column names to be sorted in descending order 9-May 21-Jun 1-July. Any ideas how?

Answer: Don't convert your dates to string until after the pivot_table, so can do so easily with rename: df['Date'] = pd.to_datetime(df['Date']) df['is'] = df['Band_In'] / df['Speed'] * 100 out = (df.pivot_table(index=['Node', 'Interface', 'carrier'], columns='Date', values='is') .rename(columns=lambda x: x.strftime('%-d-%b')) .reset_index().rename_axis(columns=None) )  Output:  Node Interface carrier 1-May 9-May 2-Jun 21-Jun 1-Jul 19-Jul 0 Server1 wan1 ATT NaN 80.0 NaN NaN NaN NaN 1 Server1 wan1 Sprint NaN NaN NaN 50.0 NaN NaN 2 Server1 wan1 Verizon NaN NaN NaN NaN 30.0 NaN 3 Server2 wan1 ATT 90.0 NaN NaN NaN NaN NaN 4 Server2 wan1 Sprint NaN NaN 88.0 NaN NaN NaN 5 Server2 wan1 Verizon NaN NaN NaN NaN NaN 22.0
Question: I have created a chart in matplotlib in python, but the last line in the following code doesn't allow alignment of the bar labels outside of the graph. import matplotlib.pyplot as plt g=df.plot.barh(x=name,y=days) g.set_title("Days people showed up") g.bar_label(g.containers[0], label_type='edge')  I get a graph that looks like:  Days people showed up ----------------------- Amy |+++ 1 | Bob |+++++++++++++++ 4 | Jane |+++++++ 2 | ---|---|---|---|---|--- 1 2 3 4 5  Instead I want something like this:  Days people showed up ----------------------- Count Amy |+++ | 1 Bob |+++++++++++++++ | 4 Jane |+++++++ | 2 ---|---|---|---|---|--- 1 2 3 4 5  Is it possible to do this? It doesn't seem like it is native in matplotlib as the only options for label_type is edge or center. Is it possible to add a label to the bar labels as well?

Answer: You can use a "secondary y axis" (this is similar to the more often used "twin" axis, but is only used to add ticks and labels, not for plotting). The example below uses ax as the name for the return value or df.plot.barh() to make the code easier to match with matplotlib documentation and tutorials. For more finetuning, ax.set_ylabe() has parameters to change its position and alignment. import matplotlib.pyplot as plt import pandas as pd df = pd.DataFrame({'name': ['Amy', 'Bob', 'Jane'], 'days': [1, 4, 2]}) ax = df.plot.barh(x='name', y='days', legend=False) ax.set_title("Days people showed up") # ax.bar_label(ax.containers[0], label_type='edge') secax = ax.secondary_yaxis(location='right', functions=(lambda x: x, lambda x: x)) secax.set_yticks(range(len(df)), df['days']) secax.tick_params(length=0) secax.set_ylabel('Counts') plt.tight_layout() plt.show()