eshangj/stackoverflow_q_and_a_sample huggingface dataset:
Question: Upon looking and researching online it does seem that there a numerous way of doing this but not sure if it fits the way that I want it for. Personally I would still like my dates as this format "3/30/24". I am extracting data from some which has numerous amount of data and everything works as expected but when I tried to sort my date it just was sorting it lexicographically and this is only when I read from the csv rather than writing because I just want to to test if it could even sort in the first place after writing to it. data_to_export = { "Company Name": ["Mcdonalds","Burgerking"], "Delivery Address":["123 lake rd", "124 west rd"], "Date": ["3/30/24", "1/23/24"], "Customer Name": ["Zack", "Peter"],}   df = pd.DataFrame(data_to_export) df.to_csv(join_move_to, index=False)  Above is some of the dummy data just so you can get a quick example than below is how I save it I did have some sort of sort methods before this but it didn't work so I just deleted it but before writing to how can I let it know that I want to sort by date and keep in mind I still want my format as "(Month/Day/Year) " I do understand this is a string so it must be converted to some sort of date then sorted but I can not find a way to do that here is what I tried just so you guys can see  df = pd.DataFrame(data_to_export) df["Date"] = pd.to_datetime(df["Date"],format="%m/%d/%y") df.sort_values(by="Date", inplace=True) df.to_csv(path, index=False) 

Answer: You should pass a custom key with to_datetime to sort_values, this will use the defined logic to sort while leaving the data unchanged: (df.sort_values(by='Date', key=lambda x: pd.to_datetime(x, format='%m/%d/%y') .to_csv(path, index=False) )  Output csv: Company Name,Delivery Address,Date,Customer Name Burgerking,124 west rd,1/23/24,Peter Mcdonalds,123 lake rd,3/30/24,Zack
Question: I have a multipolygon shapefile that i want to rotate. I can do the rotation but the problem is that the roatation changes the inner vertices. This creates overlap of polygon which i dont want. This is what i have tried. import geopandas as gpd input_poly_path = "poly3.shp" gdf = gpd.read_file(input_poly_path) explode = gdf.explode(ignore_index=True) ex = explode.rotate(-90, origin="centroid") g = gpd.GeoDataFrame(columns=['geometry'], geometry='geometry') g["geometry"] = ex    https://drive.google.com/drive/folders/1HJpnNL-iXU_rReQzVcDGuyWZ8IjciKP8?usp=drive_link Link to the polygon

Answer: IIUC, you need to pass the centroid of the unary_union as the origin of the rotation : out = gpd.GeoDataFrame( geometry=gdf.rotate( angle=-90, origin=list(gdf.unary_union.centroid.coords)[0] ) )  NB: There is no need to explode the geometry, because you do not have MultiPolygons.  Used input (gdf) : import geopandas as gpd # download from Google Drive input_poly_path = ( "C:/Users/Timeless/Downloads/" "share-20240330T125957Z-001.zip!share" ) gdf = gpd.read_file(input_poly_path, engine="pyogrio")
Question: I'm writing a Tkinter program that so far creates a window with a menu bar, a File menu, and a single item. The menu is successfully created, but with two items, the first being one that I did not specify, whose name is "-----". If I don't add an item, the spontaneous one is still added. This still happens if I specify tearoff=0. Any idea why this is happening? Windows 11, Python 3.12.2, Tkinter and Tcl 8.6. import tkinter as tk window = tk.Tk() window.geometry("800x600") menubar = tk.Menu(window) window.config(menu=menubar) fileMenu = tk.Menu(menubar) fileMenu.add_command( label="Exit", command=window.destroy, ) menubar.add_cascade(label="File", menu=fileMenu, underline=0) window.mainloop() 

Answer: In that way it works. I think u put tearoff=0 in menubar instead of fileMenu. If you put your tearoff=0 in menubar it won't affect fileMenu. So, u need to specifically put tearoff=0 in specific tk.Menu() import tkinter as tk window = tk.Tk() window.geometry("800x600") menubar = tk.Menu(window) window.config(menu=menubar) fileMenu = tk.Menu(menubar,tearoff=0) fileMenu.add_command( label="Exit", command=window.destroy, ) menubar.add_cascade(label="File", menu=fileMenu, underline=0) window.mainloop()
Question: An example of my data    StreetAddress City State Zip     1 Main St 01123 Winsted CT    1 Main St Winsted CT 01123    I am trying to use regex and pandas to clean a spreadsheet that I have. The problem I am running into is that my regex code is replacing every cell in the entire column even if there is valid data in it. I tried df['Zip'] = df['StreetAddress'].str.extract(r'(\d{5})') df['StreetAddress'] = df['StreetAddress'].str.replace(r'(\d{5})', '', regex=True)  which gives me    StreetAddress City State Zip     1 Main St Winsted CT 01123   1 Main St Winsted CT     I was hoping for something more like this    StreetAddress City State Zip     1 Main St Winsted CT 01123   1 Main St Winsted CT 01123   

Answer: I would use a boolean mask, this will avoid overwriting existing data, and also be more efficient since only the relevant rows will be evaluated: add = df['StreetAddress'].str.extract(r'(\d{5})', expand=False) m = add.notna() df.loc[m, 'Zip'] = add[m] df.loc[m, 'StreetAddress'] = (df.loc[m, 'StreetAddress'] .str.replace(r' *\d{5}', '', regex=True) )  Alternatively: df['Zip'] = df['StreetAddress'].str.extract(r'(\d{5})', expand=False).fillna(df['Zip'])  Or, as suggested by @ouroboros1, to keep the original Zip: df['Zip'].fillna(df['StreetAddress'].str.extract(r'(\d{5})', expand=False))  Output:  StreetAddress City State Zip 0 1 Main St Winsted CT 01123 1 1 Main St Winsted CT 01123
Question: I have a scipy.sparse matrix (csr_matrix()). But I need to save it to a file not in the .npz format but as a regular .txt or .csv file. My problem is that I don't have enough memory to convert the sparse matrix into a regular np.array() and then save it to a file. Is there a way to have the data as a sparse matrix in memory but save it directly as a regular matrix in the form: 0 0 0 0 1 0 1 0 1  to the disk? Or is there a way to "unzip" a .npz file without loading it into memory inside Python? (like for example gunzip or unzip in Bash).

Answer: Answer to new question: import numpy as np from scipy import sparse, io A = sparse.eye(5, format='csr') * np.pi np.set_printoptions(precision=16, linewidth=1000) with open('matrix.txt', 'a') as f: for row in A: f.write(str(row.toarray()[0])) f.write('\n') # [3.141592653589793 0. 0. 0. 0. ] # [0. 3.141592653589793 0. 0. 0. ] # [0. 0. 3.141592653589793 0. 0. ] # [0. 0. 0. 3.141592653589793 0. ] # [0. 0. 0. 0. 3.141592653589793]  And with begin/end brackets: import numpy as np from scipy import sparse, io A = sparse.eye(5, format='csr') * np.pi np.set_printoptions(precision=16, linewidth=1000) with open('matrix.txt', 'a') as f: for i, row in enumerate(A): f.write('[' if (i == 0) else ' ') f.write(str(row.toarray()[0])) f.write(']' if (i == A.shape[0] - 1) else '\n') # [[3.141592653589793 0. 0. 0. 0. ] # [0. 3.141592653589793 0. 0. 0. ] # [0. 0. 3.141592653589793 0. 0. ] # [0. 0. 0. 3.141592653589793 0. ] # [0. 0. 0. 0. 3.141592653589793]]  You may have to fiddle with set_printoptions depending on your data.  Answer to original question, which did not require that the matrix be written as dense. Harwell-Boeing format is plain text: import numpy as np from scipy import sparse, io A = sparse.eye(3, format='csr') * np.pi # Default title 0 # 3 1 1 1 # RUA 3 3 3 0 # (40I2) (40I2) (3E25.16) # 1 2 3 4 # 1 2 3 # 3.1415926535897931E+00 3.1415926535897931E+00 3.1415926535897931E+00 io.hb_write('matrix.txt', A) # saves as matrix.txt A2 = io.hb_read('matrix.txt') assert not (A2 != A).nnz # efficient check for equality  So is Matrix Market: io.mmwrite('matrix', A) # saves as matrix.mtx # %%MatrixMarket matrix coordinate real symmetric # % # 3 3 3 # 1 1 3.141592653589793e+00 # 2 2 3.141592653589793e+00 # 3 3 3.141592653589793e+00 A2 = io.mmread('matrix') assert not (A2 != A).nnz   If you want an even simpler format, although it involves more code: import numpy as np from scipy import sparse A = sparse.eye(10, format='csr')*np.pi np.savetxt('data.txt', A.data) np.savetxt('indices.txt', A.indices, fmt='%i') np.savetxt('indptr.txt', A.indptr, fmt='%i')  To load: data = np.loadtxt('data.txt') indices = np.loadtxt('indices.txt', dtype=np.int32) indptr = np.loadtxt('indptr.txt', dtype=np.int32) A2 = sparse.csr_matrix((data, indices, indptr)) assert not (A2 != A).nnz  But the important idea is that all you need to save are the data, indices, and indptr attributes of the csr_matrix.
Question: Say I have this dataframe: >>> import polars >>> df = polars.DataFrame(dict(j=['1.2', '1.2k', '1.2M', '-1.2B'])) >>> df shape: (4, 1) ┌───────┐ │ j │ │ --- │ │ str │ ╞═══════╡ │ 1.2 │ │ 1.2k │ │ 1.2M │ │ -1.2B │ └───────┘  How would I go about parsing the above to get: >>> df = polars.DataFrame(dict(j=[1.2, 1_200, 1_200_000, -1_200_000_000])) >>> df shape: (4, 1) ┌───────────┐ │ j │ │ --- │ │ f64 │ ╞═══════════╡ │ 1.2 │ │ 1200.0 │ │ 1.2e6 │ │ -1.2000e9 │ └───────────┘ >>> 

Answer: You can use str.extract() and str.strip_chars() to split the parts and then get the resulting number by using Expr.replace() + Expr.pow(): df.with_columns( pl.col('j').str.strip_chars('KMB').cast(pl.Float32) * pl.lit(10).pow( pl.col('j').str.extract(r'(K|M|B)').replace(['K','M','B'],[3,6,9]).fill_null(0) ) ) ┌─────────────┐ │ j │ │ --- │ │ f64 │ ╞═════════════╡ │ 1.2 │ │ 1200.000048 │ │ 1.2000e6 │ │ -1.2000e9 │ └─────────────┘
Question: I am testing the m.if3 function in gekko by using conditional statements of if-else but I get two different outputs. The optimal number i get from below code is 12. I plug that in the next code with if-else statement to ensure that the cost matches up but it does not. Am I using if3/if2 incorrectly? The rate is 0.1 for the first 5 days and it switches to 0.3 for the remaining 45 days. I get different outputs even though I am doing the same thing in both of them. I tried everything from using if-else statements to using if2 statements.

Answer: The if2 or if3 function isn't needed because the switching argument duration-5 is a constant value that is not a function of a Gekko variable. Just like the validation script, the two segments can be calculated separately and added together to get a total cost and patient count. from gekko import GEKKO m = GEKKO(remote=False) # parameters cost_p = 9 cost_s = 12 var1 = 50 duration = 50 x = m.Var(integer=True, lb=1) rate1 = 0.3 rate2 = 0.1 cost1 = m.Intermediate((rate1 * cost_p * 5 + cost_s) * x) cost2 = m.Intermediate((rate2 * cost_p * (duration-5) + cost_s) * x) cost = m.Intermediate(cost1+cost2) countp1 = m.Intermediate(rate1 * 5 * x) countp2 = m.Intermediate(rate2 * (duration-5) * x) p_count = m.Intermediate(countp1+countp2) m.Minimize(cost) m.Equation(p_count >= var1) m.options.SOLVER = 1 # for MINLP solution m.solve(disp=False) num_sites = x.value[0] print(f'num_s = {num_s}') print(f'cost: {cost.value[0]}') print(f'p_count: {p_count.value[0]}')  The optimal solution is: num_s = 9.0 cost: 810.0 p_count: 54.0  The solution validation agrees with this answer: # Solution validation # Parameters cost_s = 9 cost_p = 12 num_p = 50 duration = 50 if duration > 5: rate = 0.1 else: rate = 0.3 x = 9 cost1 = (0.1 * cost_p * 45 + cost_s) * x cost2 = (0.3 * cost_p * 5 + cost_s) * x cost = cost1 + cost2 countp1 = 0.3 * 5 * x countp2 = 0.1 * 45 * x countp = countp1 + countp2 print(f'cost (validation): {cost}') print(f'count (validation): {countp}')
Question: In this call to df.sort_index() on a MultiIndex dataframe, how to use func_2 for level two? func_1 = lambda s: s.str.lower() func_2 = lambda x: np.abs(x) m_sorted = df_multi.sort_index(level=['one', 'two'], key=func_1)  The documentation says "For MultiIndex inputs, the key is applied per level", which is ambiguous.  import pandas as pd import numpy as np np.random.seed(3) # Create multiIndex choice = lambda a, n: np.random.choice(a, n, replace=True) df_multi = pd.DataFrame({ 'one': pd.Series(choice(['a', 'B', 'c'], 8)), 'two': pd.Series(choice([1, -2, 3], 8)), 'A': pd.Series(choice([2,6,9,7] ,8)) }) df_multi = df_multi.set_index(['one', 'two']) # Sort MultiIndex func_1 = lambda s: s.str.lower() func_2 = lambda x: np.abs(x) m_sorted = df_multi.sort_index(level=['one'], key=func_1) 

Answer: sort_index takes a unique function as key that would be used for all levels. That said, you could use a wrapper function to map the desired sorting function per level name: def sorter(level, default=lambda x: x): return { 'one': lambda s: s.str.lower(), 'two': np.abs, }.get(level.name, default)(level) df_multi.sort_index(level=['one', 'two'], key=sorter)  NB. in case of no match a default function is used that returns the level unchanged. Another option with numpy.lexsort instead of sort_index: # levels, functions in desired sorting order sorters = [('one', lambda s: s.str.lower()), ('two', np.abs)] out = df_multi.iloc[np.lexsort([f(df_multi.index.get_level_values(lvl)) for lvl, f in sorters[::-1]])]  lexsort uses the major keys last, thus the [::-1] Output:  A one two a 1 6 -2 2 3 7 B 1 6 -2 7 -2 7 3 2 3 6
Question: I encountered the following problem: I have a pandas dataframe that looks like this.    id_tranc sum bid     1 4000 2.3%   1 20000 3.5%   2 100000 if >=100 000 - 1.6%, if < 100 000 - 100$   3 30000 if >=100 000 - 1.6%, if < 100 000 - 100$   1 60000 500$    code_to_create_dataset: dataframe = pd.DataFrame({ 'id_tranc': [1, 1, 2, 3, 1], 'sum': [4000, 20000, 100000, 30000, 60000], 'bid': ['2.3%', '3.5%', 'if >=100 000 - 1.6%, if < 100 000 - 100$', 'if >=100 000 - 1.6%, if < 100 000 - 100$', '500$']})  Necessary to calculated 'commission', depending columns 'sum' and 'bid'. Final dataframe should be look like:    id_tranc sum bid comission     1 4000 2.3% 92   1 20000 3.5% 700   2 100000 if >=100 000 - 1.6%, if < 100 000 - 100$ 1600   3 30000 if >=100 000 - 1.6%, if < 100 000 - 100$ 100   1 60000 500$ 500    If calculated with df['commission'] = df['sum'] * df['bid'] - getting result only for first 2 record. Please tell me how to do this correctly.

Answer: I would write a small parser based on a regex and operator: from operator import ge, lt, gt, le import re def logic(value, bid): # define operators, add other ones if needed ops = {'>=': ge, '>': gt, '<': lt, '<=': le} # remove spaces, split conditions on comma conditions = bid.replace(' ', '').split(',') # then loop over them, the first match will be used for cond in conditions: # extract operator, threshold, commission, unit m = re.search('(?:if(\W+)(\d+)-)?(\d+\.?\d*)([%$])', cond) if not m: # if no match, ignore continue op, thresh, com, unit = m.groups() # if no condition or condition is valid if (not op) or (op and ops[op](value, float(thresh))): if unit == '%': # handle % case return value * float(com)/100 elif unit == '$': # handle fixed com return float(com) df['comission'] = [logic(val, bid) for val, bid in zip(df['sum'], df['bid'])] # or with apply, which is less efficient # df['comission'] = df.apply(lambda row: logic(row['sum'], row['bid']), axis=1)  Output:  id_tranc sum bid comission 0 1 4000 2.3% 92.0 1 1 20000 3.5% 700.0 2 2 100000 if >=100 000 - 1.6%, if < 100 000 - 100$ 1600.0 3 3 30000 if >=100 000 - 1.6%, if < 100 000 - 100$ 100.0 4 1 60000 500$ 500.0  Regex: regex demo (?:if(\W+)(\d+)-)? # optionally match a condition (operator and threshold) (\d+\.?\d*) # match the value of the commission ([%$]) # match type of commission (% or $)  Reproducible input: df = pd.DataFrame({'id_tranc': [1, 1, 2, 3, 1], 'sum': [4000, 20000, 100000, 30000, 60000], 'bid': ['2.3%', '3.5%', 'if >=100 000 - 1.6%, if < 100 000 - 100$', 'if >=100 000 - 1.6%, if < 100 000 - 100$', '500$']})
Question: I have been trying to wrap a fortran module that takes several 1-dimensional arrays and returns calculated values CTP and HILOW. subroutine ctp_hi_low ( nlev_in, tlev_in, qlev_in, plev_in, & t2m_in , q2m_in , psfc_in, CTP, HILOW , missing ) implicit none ! ! Input/Output Variables ! integer, intent(in ) :: nlev_in ! *** # of pressure levels real(4), intent(in ) :: missing ! *** missing value - useful for obs real(4), intent(in ), dimension(nlev_in) :: tlev_in ! *** air temperature at pressure levels [K] real(4), intent(in ), dimension(nlev_in) :: qlev_in ! *** specific humidity levels [kg/kg] real(4), intent(in ), dimension(nlev_in) :: plev_in ! *** pressure levels [Pa] real(4), intent(in ) :: psfc_in ! *** surface pressure [Pa] real(4), intent(in ) :: t2m_in ! *** 2-meter temperature [K] real(4), intent(in ) :: q2m_in ! *** 2-meter specific humidity [kg/kg] real(4), intent(out) :: CTP ! *** Convective Triggering Potential [K] real(4), intent(out) :: HILOW ! *** Low-level humidity [K] <internal variables and calculations>  The wrapping was successful, but when running the program I receive the following error. I have been passing dummy data to ensure that all arrays have the same shape. Traceback (most recent call last): File "/mnt/d/ResearchProjects/NSF-Drought/coupling-metrics/minimal/test.py", line 47, in <module> conv_trig_pot_mod.ctp_hi_low(10, arr, arr, arr, 1,1, 1) ValueError: ctp_hilow.ctp_hilow.conv_trig_pot_mod.ctp_hi_low: failed to create array from the 2nd argument `qlev_in` -- 0-th dimension must be fixed to 1 but got 10  I have been trying to pass my 1-D arrays in varying dimensions, but all of them produce the same error. arr = np.ones([10,], order='F') arr = np.ones([10,1], order='F') arr = np.ones([1,10], order='F')  I also have been trying to pass a list. I simply cannot figure out how to satisfy f2py's shape requirement. Any help is appreciated

Answer: It is better to call fortran subroutine from python with explicit argument name: conv_trig_pot_mod.ctp_hi_low(nlev_in=10, tlev_in=arr, qlev_in=arr, plev_in=arr, t2m_in=1, q2m_in=1, psfc_in=????, missing=1)  The order of arguments differs between Fortran and Python. you can check the new arguments list and order __doc__ in python: # import may be different for different python version from xxxx import yyyy as ctp_module print(ctp_module.ctp_hi_low.__doc__)
Question: I have this code, which prints 1: s = 0 def dfs(n): global s if n > 10: return 0 s += dfs(n + 1) return n dfs(0) print(s)  If I modify dfs like this： def dfs(n): global s if n > 10: return 0 i = dfs(n + 1) s += i return n  it will print 55 I know what is a better way to write the dfs. I just want to know why the value of s is different after the call of two dfs

Answer: Python is interpreted and executed from top to bottom, so in the first version you have: s += dfs(n + 1)  which is exact as: s = s + dfs(n + 1)  So when you do this recursively you have on stack these commands: s = 0 + dfs(1) # <-- dfs(1) will return 1 s = 0 + dfs(2) # <-- dfs(2) will return 2 ... s = 0 + dfs(9) # <-- dfs(9) will return 9 s = 0 + dfs(10) # <-- dfs(10) will return 10 s = 0 + dfs(11) # <-- dfs(11) will return 0  So when you observe, the last step is s = 0 + 1 and you see the 1 as final result.  The second version i = dfs(n + 1) s += i  You assign to s after dfs(n + 1) is evaluated so you see the final answer 55  NOTE: If you rewrite the first version s += dfs(n + 1) to s = dfs(n + 1) + s you will see the result 55 too.
Question: I have recently exported a table from R's TukeyHSD test to obtain the p-values for various time groups (0, 5, 10, 20, 30, 40, 50, 60). I'm curious if there's a method to transform this into a correlation matrix, where each axis represents the time groups and corresponds to the respective p-value. The table includes an index indicating the correspondence between the different time groups (e.g., 5-10 or 10-50). I've imported it as a dataframe into Python. Is there a way to rearrange the dataframe as depicted below?  p adj Groups 50-0 2.815526e-13 60-0 2.855494e-13 20-0 4.764197e-08 50-5 1.712389e-05 50-10 1.483440e-04 50-40 1.643480e-04 60-5 5.873007e-04 60-10 5.218047e-03 60-40 5.613566e-03 10-0 6.878476e-03 40-0 1.270855e-02 20-5 7.380859e-02 50-20 1.574372e-01 40-20 3.264569e-01 20-10 3.369147e-01 5-0 3.816166e-01 60-50 7.301423e-01 60-20 8.503578e-01 10-5 9.731384e-01 40-5 9.820983e-01 40-10 1.000000e+00  I want it to be something like:  0 5 10 20 ... 0 ... ... ... ... 5 ... ... ... ... 10 ... ... ... ... 20 ... ... ... ... ...  I haven't found anything similar online, so I don't know where to start.

Answer: Try: df[["x", "y"]] = df.index.str.split("-", expand=True).to_frame().astype(int).values print(pd.crosstab(df["x"], df["y"], df["p adj"], aggfunc="first"))  Prints: y 0 5 10 20 40 50 x 5 3.816166e-01 NaN NaN NaN NaN NaN 10 6.878476e-03 0.973138 NaN NaN NaN NaN 20 4.764197e-08 0.073809 0.336915 NaN NaN NaN 40 1.270855e-02 0.982098 1.000000 0.326457 NaN NaN 50 2.815526e-13 0.000017 0.000148 0.157437 0.000164 NaN 60 2.855494e-13 0.000587 0.005218 0.850358 0.005614 0.730142
Question: What's the best way to apply a custom function to multiple columns in Polars? Specifically I need the function to reference another column in the dataframe. Say I have the following: df = pl.DataFrame({ 'group': [1,1,2,2], 'other': ['a', 'b', 'a', 'b'], 'num_obs': [10, 5, 20, 10], 'x': [1,2,3,4], 'y': [5,6,7,8], })  And I want to group by group and calculate an average of x and y, weighted by num_obs. I can do something like this: variables = ['x', 'y'] df.group_by('group').agg((pl.col(var) * pl.col('num_obs')).sum()/pl.col('num_obs').sum() for var in variables)  but I'm wondering if there's a better way. Also, I don't know how to add other aggregations to this approach, but is there a way that I could also add pl.sum('n_obs')?

Answer: You can just pass list of columns into pl.col(): df.group_by('group').agg( (pl.col('x','y') * pl.col('num_obs')).sum() / pl.col('num_obs').sum(), pl.col('num_obs').sum() ) ┌───────┬──────────┬──────────┬─────────┐ │ group ┆ x ┆ y ┆ num_obs │ │ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ f64 ┆ f64 ┆ i64 │ ╞═══════╪══════════╪══════════╪═════════╡ │ 1 ┆ 1.333333 ┆ 5.333333 ┆ 15 │ │ 2 ┆ 3.333333 ┆ 7.333333 ┆ 30 │ └───────┴──────────┴──────────┴─────────┘
Question: Is there a way to manually set the values shown in the legend of a seaborn (or matplotlib) scatterplot when the legend contains a continuous variable (hue)? For example, in the plot below I might like to show the colors corresponding to values of [0, 1, 2, 3] rather than [1.5, 3, 4.5, 6, 7.5] np.random.seed(123) x = np.random.randn(500) y = np.random.randn(500) z = np.random.exponential(1, 500) fig, ax = plt.subplots() hue_norm = (0, 3) sns.scatterplot( x=x, y=y, hue=z, hue_norm=hue_norm, palette='coolwarm', ) ax.grid() ax.set(xlabel="x", ylabel="y") ax.legend(title="z") sns.despine()  

Answer: Seaborn creates its scatterplot a bit different than matplotlib. That way, the scatterplot can be customized in more ways. For the legend, Seaborn 0.13 employs custom Line2D elements (older Seaborn versions use PathCollections). The following approach:  replaces Seaborn's hue_norm=(0, 3) with an equivalent matplotlib norm creates dummy Line2D elements to serve as legend handles copies all properties (size, edgecolor, ...) of the legend handle created by Seaborn then changes the marker color depending on the norm and colormap  The approach might need some tweaks if your scatterplot differs. The code has been tested with Matplotlib 3.8.3 and Seaborn 0.13.2 (and 0.12.2). import matplotlib.pyplot as plt import seaborn as sns import numpy as np from matplotlib.lines import Line2D np.random.seed(123) x = np.random.randn(500) y = np.random.randn(500) z = np.random.exponential(1, 500) fig, ax = plt.subplots() hue_norm = plt.Normalize(vmin=0, vmax=3) sns.scatterplot(x=x, y=y, hue=z, hue_norm=hue_norm, palette='coolwarm', ax=ax) legend_keys = [0, 1, 2, 3] handles = [Line2D([], []) for _ in legend_keys] cmap = plt.get_cmap('coolwarm') for h, key in zip(handles, legend_keys): if type(ax.legend_.legend_handles[0]) == Line2D: h.update_from(ax.legend_.legend_handles[0]) else: h.set_linestyle('') h.set_marker('o') h.set_markeredgecolor(ax.legend_.legend_handles[0].get_edgecolor()) h.set_markeredgewidth(ax.legend_.legend_handles[0].get_linewidth()) h.set_markerfacecolor(cmap(hue_norm(key))) h.set_label(f'{key}') ax.legend(handles=handles, title='z') sns.despine() plt.show()