Why is it scipy.stats.gaussian_kde() slower than seaborn.kde_plot() for the same data?
In python 3.7, I have this numpy array with shape=(2, 34900). This arrays is a list of coordinates where the index 0 represents the X axis and the index 1 represents the y axis.
When I use seaborn.kde_plot() to make a visualization of the distribution of this data, I'm able to get the result in about 515 seconds when running on a i5 7th generation.
But when I try to run the following piece of code:
#Find the kernel for
k = scipy.stats.kde.gaussian_kde(data, bw_method=.3)
#Define the grid
xi, yi = np.mgrid[0:1:2000*1j, 0:1:2000*1j]
#apply the function
zi = k(np.vstack([xi.flatten(), yi.flatten()]))
which finds the gaussian kernel for this data and applies it to a grid I defined, it takes much more time. I wasn't able to run the full array but when running on a slice with the size of 140, it takes about 40 seconds to complete.
The 140 sized slice does make an interesting result which I was able to visualize using plt.pcolormesh()
.
My question is what I am missing here. If I understood what is happening correctly, I'm using the scipy.stats.kde.gaussian_kde()
to create an estimation of a function defined by the data. Then I'm applying the function to a 2D space and getting it's Z component as result. Then I'm plotting the Z component. But how can this process be any different from
seaborn.kde_plot()
that makes the code take so much longer.
Scipy's implementation just goes through each point doing this:
for i in range(self.n):
diff = self.dataset[:, i, newaxis]  points
tdiff = dot(self.inv_cov, diff)
energy = sum(diff*tdiff,axis=0) / 2.0
result = result + exp(energy)
1 answer

Seaborn has in general two ways to calculate the bivariate kde. If available, it uses
statsmodels
, if not, it falls back toscipy
.The scipy code is similar to what is shown in the question. It uses
scipy.stats.gaussian_kde
. The statsmodels code usesstatsmodels.nonparametric.api.KDEMultivariate
.However, for a fair comparisson we would need to take the same grid size for both methods. The standard gridsize for seaborn is 100 points.
import numpy as np; np.random.seed(42) import seaborn.distributions as sd N = 34900 x = np.random.randn(N) y = np.random.randn(N) bw="scott" gridsize=100 cut=3 clip = [(np.inf, np.inf), (np.inf, np.inf)] f = lambda x,y : sd._statsmodels_bivariate_kde(x, y, bw, gridsize, cut, clip) g = lambda x,y : sd._scipy_bivariate_kde(x, y, bw, gridsize, cut, clip)
If we time those two functions,
# statsmodels %timeit f(x,y) # 1 loop, best of 3: 16.4 s per loop # scipy %timeit g(x,y) # 1 loop, best of 3: 8.67 s per loop
Scipy is hence twice as fast as statsmodels (the seaborn default). The reason why the code in the question takes so long is that instead of a grid of size 100, a grid of size 2000 is used.
Seeing those results one would actually be tempted to use scipy instead of statsmodels. Unfortunately it does not allow to choose which one to use. One hence needs to manually set the respective flag.
import seaborn.distributions as sd sd._has_statsmodels = False # plot kdeplot with scipy.stats.kde.gaussian_kde sns.kdeplot(x,y)
See also questions close to this topic

How does sklearn GridsearchCV in combination with pipline work?
I've some trouble understanding how the GridsearchCV really works, in combination with a self defined transformator.
What I want to achieve? I want to implement a Transformer/Estimator, which allows switching between some methods depending on a parameter, because I want to include these different methods inside the gridsearch.
Example: I have a self defined Transformer called Scaler(), which either chooses MinMaxScaler or StandardScaler. (just for simplicity)
class Scaling(): def __init__(self, **params): self.method=None self.params = {} print("INITIATING CLASS") def fit(self, X, y=None): return self def transform(self, X): print("TRANSFORMING", X) if self.method == "minMax": self.scaler = MinMaxScaler(feature_range=self.params["feature_range"]) elif self.method == "std": self.scaler = StandardScaler() return self.scaler.fit_transform(X) def get_params(self, **params): return {**StandardScaler().get_params(), **MinMaxScaler().get_params(), **{"method":""} } def set_params(self, **params): print("SETTING PARAMETER") self.method = params["method"] self.params = params
This is my example data:
data = np.array([1,2,3,4,5,6,7,8,9,10]).reshape(1,1) y = [2,3,4,5,6,7,8,9,10,11]
My pipline:
p = Pipeline([('scaler', Scaling()), ('model', LinearRegression())])
My paramgrid and Gridsearch
hyperparams = { 'scaler__feature_range' : [(0,1), (100,10)], 'scaler__method':["minMax"] } clf = GridSearchCV(p,hyperparams, cv=2) clf.fit(data, y)
It actually works, but I'm really confused about the printing logs:
INITIATING CLASS INITIATING CLASS INITIATING CLASS SETTING PARAMETER TRANSFORMING [[ 6][ 7][ 8][ 9][10]] TRANSFORMING [[1][2][3][4][5]] TRANSFORMING [[ 6][ 7][ 8][ 9][10]] INITIATING CLASS SETTING PARAMETER TRANSFORMING [[1][2][3][4][5]] TRANSFORMING [[ 6][ 7][ 8][ 9][10]] TRANSFORMING [[1][2][3][4][5]] INITIATING CLASS SETTING PARAMETER TRANSFORMING [[ 6][ 7][ 8][ 9][10]] TRANSFORMING [[1][2][3][4][5]] TRANSFORMING [[ 6][ 7][ 8][ 9][10]] INITIATING CLASS SETTING PARAMETER TRANSFORMING [[1][2][3][4][5]] TRANSFORMING [[ 6][ 7][ 8][ 9][10]] TRANSFORMING [[1][2][3][4][5]] INITIATING CLASS SETTING PARAMETER TRANSFORMING [[ 1][ 2 [ 3][ 4][ 5][ 6][ 7][ 8][ 9][10]]
I have set cv=2. I would expect it like this. 1. Instatiate all the Transformers 2. set the parameters according the the Gridsearch 3. pass the trainfold through the pipline 4. pass the testfold through the pipline 5. repeat So I would have expect 8 calls to the tranformer method because we need one for the train and one for the test fold. Because of cv=2 we do this 2 times and because we are defining two different values for feature_range inside the paramgrid, we have to multiply it with 2, therefore 8. Whats wrong?
But why are there so many calls of my Scaling class? How can this order of logs be explained? Why is the fully sequence at the end transformed?
Thanks for helping!

How to turn multiple rows into multiple headers headers in pandas dataframe
So I have a pandas dataFrame and I would like to turn two rows into multiple headers. so from
1 A  A  B  B 2 C  D  C  D 3 cat dogmousegoose
to
A  B C  D  C  D 1 cat dogmousegoose
I found
df.columns = df.iloc[0]
to work for 1 row but i want multiple headers from the first and second row Thanks in advance!

VTK rescale to visible data range
I for some reason can not manage to find a reasonable way to rescale the plot color to the current camera setting through the python api. Is there any way where i can just get a vtkDoubleArray of the currently viewed data from which i can call GetRange() to update my DataMapper? A simplified version of my current code looks something like this:
import vtk file_name = 'foo.vtk' field_name = "bar" reader = vtk.vtkUnstructuredGridReader() reader.SetFileName(file_name) reader.ReadAllScalarsOn() reader.ReadAllVectorsOn() reader.Update() output = reader.GetOutput() numvals = 1024 ctf = vtk.vtkColorTransferFunction() ctf.SetColorSpaceToRGB() ctf.AddRGBPoint(1, 1, 1, 0) ctf.AddRGBPoint(0, 0, 0, 0) ctf.AddRGBPoint(0.293069/1.72393, 0, 0, 1) ctf.AddRGBPoint(0.586138/1.72393, 0, 1, 1) ctf.AddRGBPoint(0.861967/1.72393, 0, 1, 0) ctf.AddRGBPoint(1.155040/1.72393, 1, 1, 0) ctf.AddRGBPoint(1.448100/1.72393, 1, 0, 0) ctf.AddRGBPoint(1.723930/1.72393, 0.87843, 0, 1) lut = vtk.vtkLookupTable() lut.SetNumberOfTableValues(numvals) lut.Build() for i in range(0,numvals): rgb = list(ctf.GetColor(float(i)/numvals))+[1] lut.SetTableValue(i,rgb) mapper = vtk.vtkDataSetMapper() mapper.SetInputData(output) mapper.ScalarVisibilityOn() mapper.SetColorModeToMapScalars() mapper.SetLookupTable(lut) mapper.SetScalarModeToUsePointFieldData() mapper.SelectColorArray(field_name) mapper.SetScalarRange(output.GetPointData().GetArray(field_name).GetRange()) actor = vtk.vtkActor() actor.SetMapper(mapper) camera = vtk.vtkCamera() camera.SetPosition(0.5, 0.3, 3); camera.SetFocalPoint(0.5, 0.3, 0); renderer = vtk.vtkRenderer() renderer.AddActor(actor) renderer.SetBackground(1, 1, 1) renderer.SetActiveCamera(camera) render_window = vtk.vtkRenderWindow() render_window.AddRenderer(renderer) render_window.SetSize(800,800) render_window.Render() windowToImageFilter = vtk.vtkWindowToImageFilter() windowToImageFilter.SetInput(render_window) #windowToImageFilter.SetInputBufferTypeToRGBA() windowToImageFilter.ReadFrontBufferOff() windowToImageFilter.Update() writer = vtk.vtkPNGWriter() writer.SetFileName("screenshot.png"); writer.SetInputConnection(windowToImageFilter.GetOutputPort()); writer.Write();

trapz integration on dataframe index after grouping
I have some data and I want to first groupby some interval the A column and then integrate the target column by index spacing.
import numpy as np import pandas as pd from scipy import integrate df = pd.DataFrame({'A': np.array([100, 105.4, 108.3, 111.1, 113, 114.7, 120, 125, 129, 130, 131, 133,135,140, 141, 142]), 'B': np.array([11, 11.8, 12.3, 12.8, 13.1,13.6, 13.9, 14.4, 15, 15.1, 15.2, 15.3, 15.5, 16, 16.5, 17]), 'C': np.array([55, 56.3, 57, 58, 59.5, 60.4, 61, 61.5, 62, 62.1, 62.2, 62.3, 62.5, 63, 63.5, 64]), 'Target': np.array([4000, 4200.34, 4700, 5300, 5800, 6400, 6800, 7200, 7500, 7510, 7530, 7540, 7590, 8000, 8200, 8300])}) df['y'] = df.groupby(pd.cut(df.iloc[:, 3], np.arange(0, max(df.iloc[:, 3]) + 100, 100))).sum().apply(lambda g: integrate.trapz(g.Target, x = g.index))
Above code gives me:
AttributeError: ("'Series' object has no attribute 'Target'", 'occurred at index A')
If I try this:
colNames = ['A', 'B', 'C', 'Target'] df['z'] = df.groupby(pd.cut(df.iloc[:, 3], np.arange(0, max(df.iloc[:, 3]) + 100, 100))).sum().apply(lambda g: integrate.trapz(g[colNames[3]], x = g.index))
I get:
TypeError: 'str' object cannot be interpreted as an integer During handling of the above exception, another exception occurred: .... KeyError: ('Target', 'occurred at index A')

can't get to work trapz using index from pandas dataframe
import numpy as np import pandas as pd from scipy import integrate df = pd.DataFrame({'A': np.array([100, 105.4, 108.3, 111.1, 113, 114.7, 120, 125, 129, 130, 131, 133,135,140, 141, 142]), 'B': np.array([11, 11.8, 12.3, 12.8, 13.1,13.6, 13.9, 14.4, 15, 15.1, 15.2, 15.3, 15.5, 16, 16.5, 17]), 'C': np.array([55, 56.3, 57, 58, 59.5, 60.4, 61, 61.5, 62, 62.1, 62.2, 62.3, 62.5, 63, 63.5, 64]), 'Target': np.array([4000, 4200.34, 4700, 5300, 5800, 6400, 6800, 7200, 7500, 7510, 7530, 7540, 7590, 8000, 8200, 8300])}) df['y'] = df.apply(lambda g: integrate.trapz(g.A, x = g.index))
the above call gives me:
AttributeError: ("'Series' object has no attribute 'A'", 'occurred at index A')
If I use:
colNames = ['A', 'B', 'C', 'Target'] df['y'] = df.apply(lambda g: integrate.trapz(g[colNames[0]], x = g.index))
it gives :
TypeError: an integer is required During handling of the above exception, another exception occurred: .... KeyError: ('A', 'occurred at index A')
I want to create a new column with the integrated values.

Error with SciPy integration: only size1 arrays can be converted to Python scalars
I have the following simple code where I am using integration (involving a Bessel function) from SciPy.
import matplotlib.pyplot as plt import numpy as np import scipy.integrate as integrate import scipy.special as sp from scipy.special import jn U = np.arange(0.0, 10.0, 0.1) #U = np.linspace(0,10,100) def intgl(U): #intgl = integrate.quad(lambda x: jn(1,x)/(x*(1.0+np.exp(U*x*0.5))), 0.1, 1000) intgl, err = integrate.quad(lambda x: sp.jv(1,x)/(x*(1.0+np.exp(U*x*0.5))), 0, 100) return intgl Del = U  4.0 + 8.0*intgl(U) print ("U, Del=",U, Del) plt.plot(U,Del) plt.show()
However, I am encountering error message:
Traceback (most recent call last): File "test.py", line 18, in <module> Del = U  4.0 + 8.0*intgl(U) File "test.py", line 14, in intgl intgl, err = integrate.quad(lambda x: sp.jv(1,x)/(x*(1.0+np.exp(U*x*0.5))), 0, 100) File "/usr/local/lib/python3.7/sitepackages/scipy/integrate/quadpack.py", line 341, in quad points) File "/usr/local/lib/python3.7/sitepackages/scipy/integrate/quadpack.py", line 448, in _quad return _quadpack._qagse(func,a,b,args,full_output,epsabs,epsrel,limit) TypeError: only size1 arrays can be converted to Python scalars
What could be happening and how can one fix it? Hope I am calling the function intgl correctly

Jmeter master slave connection time out error
we are facing issue, while trying to run jmeter distribution testing with master and slave configuration on different machines. Jmeter distribution test is running fine on same machine , but we are getting Connection refused to host: xxx.xxx.xxx.xx; nested exception is:java.net.ConnectException: Connection timed out: connect Failed to configure xxx.xxx.xxx.xxx

How can one perform grouping in Binomial distribution using R?
I have Fit a binomial distribution for random 300 binomial numbers of 10 trials of probability 0.7. I have used the below R code to generate the distribution, I need to perform grouping, how should I go about it in R.
a = rbinom(300,10,0.7) a b = table(a) b c = matrix(b) c d = cbind(2:10,c) d e = rbind(c(1,0),d) e f = 10 phat = mean(a)/300 exp = vector() for ( i in 0:f) { exp[i] = dbinom(i,f,phat)#raeturns probability of a distribuition } exp exp.probs = 300*exp exp.probs f = data.frame(e,round(exp.probs)) f

Kmoment for exponential distribution
I'm trying to find kmoment for exponential distribution using characteristic function but when I calculate first, second, third moment I dont see any dependencies.

How to plot confusion matrices of different types in Python with minimal code?
I have 2
numpy
arrays,y_actual
(actual values) andy_pred
(ML model predictions), both having binary values, either 0 or 1.Currently, I am forming a
confusion matrix
based on the following syntax:df_confusion = pd.crosstab(y_actual, y_pred, rownames=['Actual'], colnames=['Predicted'], margins=True) print('Confusion Matrix:') print(df_confusion)
However, I checked out SO and other documentation and couldn't find a comprehensive example with minimal code, which can help me accomplish the following:
 Form a confusion matrix figure (using
matplotlib
orSeaborn
, in which the actual number of samples for each class are displayed, alongside, there is name labels for 0 asClass A
, and 1 asClass B
.  Plot a similar confusion matrix plot, in which the percentage of samples which lie in each class are displayed (such as true positive rate, false positive rate etc.) within the confusion matrix.
 Plot a further confusion matrix, which shows the confusion matrix, along with a scale on right hand side showing number of samples (like this) https://scikitlearn.org/stable/_images/sphx_glr_plot_confusion_matrix_001.png. Form a similar confusion matrix for normalised case, like https://scikitlearn.org/stable/_images/sphx_glr_plot_confusion_matrix_001.png.
Also, as stated in the question, the aim is to accomplish labelling of Class 0 as
Class A
and Class 1 asClass B
, as presently these show only as0
and1
, not looking very coherent. I would prefer minimal code to accomplish the same, and generate coherent and nice looking Confusion Matrix plots. Any help in this regard is highly appreciated.  Form a confusion matrix figure (using

How do I set the xcoordinate in a swarmplot in seaborn?
Trying to do a swarmplot with 3 different vectors in seaborn. I'd like to have each vector at a different xcoordinate and with a different colour.
Unfortunately all the tutorials have the data in some format I can't really find an explanation / manual for... This is what I've got so far:
#!/usr/bin/env python3 import numpy as np import matplotlib.pyplot as plt import seaborn import pandas as pd # Create figure fig = plt.figure() ax = fig.add_subplot(111) ax.set_ylabel('Evaluations') colors = [ 'tab:orange', 'tab:green', 'tab:red', ] labels = [ 'Method 2', 'Method 3', 'Method 4', ] data = [ [1, 2.1, 3.2, 4.5, 3.6, 2.7, 1.4], [2.2, 4.7, 5.1, 4.4, 3.8, 5, 3.4], [8.4, 7.2, 6.1, 5.4, 8.1, 7.4, 6.8], ] data = np.array(data).T df = pd.DataFrame(data, columns=labels) seaborn.swarmplot(data=df, y='Method 2', color=colors[0]) seaborn.swarmplot(data=df, y='Method 3', color=colors[1]) seaborn.swarmplot(data=df, y='Method 4', color=colors[2]) plt.show()
This almost works, but plots everything on the same axis:
Also, the label should be on the xaxis not the yaxis. Pretty sure I'm missing something really basic here. Anyone?

seaborn heatmap autoordering labels to smoothen color shifts
I was wondering if there is a builtin functionality or at least a 'smart' way of ordering x and ylabels by their values in combination with seaborn heatmaps.
Let's say the unordered heatmap looks like this:
However, the goal is to reorder the labels having the color shifts 'smoothened'. It should look more like this afterwards:
Thanks for your advice!
Best regards

Mapping data from dataset
I was given the following question as part of a project in R. The dataset I'm working on is "Rice" within the "DAAG" package. It isn't clear to me what exactly this map is supposed to represent and what I am supposed to do. The dataset consists of 12 plants, numbered 112, receiving 6 different treatments.
The question is as follows:
As you are used to this kind of study, you know that soil quality has a big impact on rice productivity. You do not have a map or data regarding soil characteristics but Dr. ______ gave you very useful information to know which plant is where.
a. Make a list of the different PlantNo represented in your dataset.
b. Add two columns (X and Y) to the previous list where X and Y follow a Gaussian distribution of mean = 8 and sd = 2.5.
c. Create a map in which field is represented by a node
(empty_graph)
.• Node size depends on the average shoot dry mass
(vertex.size = log(…))
• Node color depends on the TREATMENT
(vertex.color = …)
• Node position depends on X and Y columns
(layout = …)
How do I move forward with this question (parts a and b in particular)?

Gaussian process regression model with multiple variables in R
I have been working on the GP with multiple variables, I am not sure whether I should use the similar code as the univariate GP or there is other methods. I try to use array as the result of the kernel function, however, it is not working. I show the code below, please tell me where I make mistakes.
## Univariate GP ## ## a covariance matrix calcSigma < function(X1,X2,l=1) { Sigma < matrix(rep(0, length(X1)*length(X2)), nrow=length(X1)) for (i in 1:nrow(Sigma)) { for (j in 1:ncol(Sigma)) { Sigma[i,j] < exp(0.5*(abs(X1[i]X2[j])/l)^2) } } return(Sigma) } # Define the points at which we want to define the functions x.star < seq(5,5,len=50) # with unknown data Sigma < calcSigma(x.star,x.star) numSamps<5 #### library(gptk) value.gp< gaussSamp(mu=matrix(0,nrow=dim(Sigma)[1]), Sigma, numSamps) value.gp<t(value.gp) value.gp< cbind(x=x.star,as.data.frame(value.gp)) value.gp < melt(value.gp,id="x") # Plot the result fig2a < ggplot(value.gp,aes(x=x,y=value)) + geom_rect(xmin=Inf, xmax=Inf, ymin=2, ymax=2, fill="grey80") + geom_line(aes(group=variable)) + theme_bw() + scale_y_continuous(lim=c(2.5,2.5), name="output, f(x)") + xlab("input, x") fig2a ############################################################################ ## ## With known data ## f < data.frame(x=c(4,3,1,0,2), y=c(2,0,1,2,1)) # Calculate the covariance matrices # using the same x.star values as above x < f$x k.xx < calcSigma(x,x) k.xxs < calcSigma(x,x.star) k.xsx < calcSigma(x.star,x) k.xsxs < calcSigma(x.star,x.star) # These matrix calculations correspond to equation (2.19) # in the book. f.star.bar < k.xsx%*%solve(k.xx)%*%f$y cov.f.star < k.xsxs  k.xsx%*%solve(k.xx)%*%k.xxs # This time we'll plot more samples. We could of course # simply plot a +/ 2 standard deviation confidence interval # as in the book but I want to show the samples explicitly here. numSamps.01 < 5 value.gp1< gaussSamp(mu=f.star.bar , cov.f.star, numSamps.01) value.gp1<t(value.gp1) value.gp1< cbind(x=x.star,as.data.frame(value.gp1)) value.gp1 < melt(value.gp1,id="x") ########### fig2b < ggplot(value.gp1,aes(x=x,y=value)) + geom_line(aes(group=variable), colour="grey80") + geom_point(data=f,aes(x=x,y=y),pch = 3,cex = 3) + theme_bw() + geom_line(aes(colour=variable)) + scale_y_continuous(lim=c(3,3), name="output, f(x)") + xlab("input, x") fig2b ############################################################################ ## ## With noisy observations ## sigma.n < 0.1 # Recalculate the mean and covariance functions f.bar.star < k.xsx%*%solve(k.xx + sigma.n^2*diag(1, ncol(k.xx)))%*%f$y cov.f.star < k.xsxs  k.xsx%*%solve(k.xx + sigma.n^2*diag(1, ncol(k.xx)))%*%k.xxs value.gp2< gaussSamp(mu=f.star.bar , cov.f.star, numSamps.01) value.gp2<t(value.gp2) value.gp2< cbind(x=x.star,as.data.frame(value.gp2)) value.gp2 < melt(value.gp2,id="x") fig2c < ggplot(value.gp2,aes(x=x,y=value)) + geom_line(aes(group=variable), colour="grey80") + geom_point(data=f,aes(x=x,y=y),pch = 3,cex = 3) + theme_bw() + geom_line(aes(colour=variable)) + scale_y_continuous(lim=c(3,3), name="output, f(x)") + geom_errorbar(data=f,aes(x=x,y=NULL,ymin=y2*sigma.n, ymax=y+2*sigma.n), width=0.2) + xlab("input, x") ############################################################################## ## ## multivariate GP ## ############################################################################## calcSigma.multi < function(X1,X2,l=1) { Sigma < array(NA, dim = c(1,ncol(X1), nrow(X1),nrow(X2))) for (i in 1:nrow(X1)) { for (j in 1:nrow(X2)) { Sigma[,,i,j] < exp(0.5*(abs(X1[i,]X2[j,])/l)^2) } } return(Sigma) } x.star < matrix(seq(5,5,len=60),nrow=6,ncol=10) ##nrow = the number of data set(observations) we have ##ncol = the number of variables(x1,x2,x3,...etc.) Sigma.multi < calcSigma.multi(x.star,x.star) numSamps<nrow(x.star) #### value.gp< gaussSamp(mu=matrix(0,nrow=dim(Sigma.multi)[3]), Sigma.multi, numSamps) ##stuck here!!!!

Sampling data points from a Gaussian Mixture Model python
I am really new to python and GMM. I learned GMM recently and trying to implement the codes from here
I met some problems when I run gmm.sample() method:
gmm16 = GaussianMixture(n_components=16, covariance_type='full', random_state=0) Xnew = gmm16.sample(400,random_state=42) plt.scatter(Xnew[:, 0], Xnew[:, 1])
the error shows:
TypeError: sample() got an unexpected keyword argument 'random_state'
I have checked the latest document and find out the method sample should only contain n which indicates that the number of samples to generate. But when I delete 'random_state=42', new error appears:
codes:
Xnew = gmm16.sample(400) plt.scatter(Xnew[:, 0], Xnew[:, 1])
error:
TypeError: tuple indices must be integers or slices, not tuple
Does anyone meet this problem when you implement the codes from Jake VanderPlas? How could I fix it?
My Jupyter:
The version of the notebook server is: 5.7.4
Python 3.7.1 (default, Dec 14 2018, 13:28:58)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.2.0  An enhanced Interactive Python. Type '?' for help.