In this section, we learn some basic scientific functions and packages. We use the NumPy, SciPy, Matplotlib, Interpolation, AstroPy and SunPy packages.
Required packages
- Python v2.7 or 3.5 or 3.6
- NumPy v1.7+
- SciPy v1.0+
- Matplotlib v2.0+
- AstroPy v2.0+
- SunPy v0.8+ (Warning ! : This package is developing package, so that some of functions can be depricated)
- Interpolation v0.18+
Above requried packages can be downloaded by using conda command in your cmd prompt or terminal as below :
conda install -c condaforge sunpy astropy interpolation
This tutorial is based on the Scipy-Lecture notes.
You can download this tutorial file on the Scientific-python FISS website below
and some examples files.
Some of calling syntaxes are differen according to python version. The main differences are print statement and division operator. In python 2, division operator '/' is the floor division, but in python3 it isn't the floor division. In python3 floor division syntax is '//' To avoid this compatibility problem, we import the "future" method.
from __future__ import division, print_function
To call arrays in python, we have to import the numpy package at first.
import numpy as np
a = np.array([1,5,2,4,3,0])
a
array([1, 5, 2, 4, 3, 0])
This array can be sort by using sort function
a.sort()
a
array([0, 1, 2, 3, 4, 5])
a.dtype
dtype('int32')
b = np.array([[0,1,2],[3,4,5]],dtype = 'float')
b
array([[ 0., 1., 2.], [ 3., 4., 5.]])
b.dtype
dtype('float64')
b.shape # Same with the size function in IDL
(2, 3)
len(b) # returns the size of the first dimension
2
convert row array to column array
c = a[:,None]
c
array([[0], [1], [2], [3], [4], [5]])
a = np.arange(6) # similar with the *indgen functions in IDL
a
array([0, 1, 2, 3, 4, 5])
b = np.arange(0,10,0.1)
b
array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. , 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2. , 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3. , 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4. , 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5. , 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6. , 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7. , 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8. , 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 9. , 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9])
np.arange?
c = np.linspace(0,10,101)
c
array([ 0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. , 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2. , 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 3. , 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4. , 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5. , 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8, 5.9, 6. , 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 6.7, 6.8, 6.9, 7. , 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9, 8. , 8.1, 8.2, 8.3, 8.4, 8.5, 8.6, 8.7, 8.8, 8.9, 9. , 9.1, 9.2, 9.3, 9.4, 9.5, 9.6, 9.7, 9.8, 9.9, 10. ])
np.linspace?
one = np.ones((3,3))
one
array([[ 1., 1., 1.], [ 1., 1., 1.], [ 1., 1., 1.]])
zero = np.zeros((2,3))
zero
array([[ 0., 0., 0.], [ 0., 0., 0.]])
np.random.seed(2018)
randu = np.random.rand(4) #uniform random number generator
randu
array([ 0.88234931, 0.10432774, 0.90700933, 0.3063989 ])
randg = np.random.randn(5) #gaussian random number generator
randg
array([ 2.14839926, -1.279487 , 0.50227689, 0.8560293 , -0.14279008])
a = np.arange(10)
a[0]
0
a[:]
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a[3:6]
array([3, 4, 5])
a[3:8:2]
array([3, 5, 7])
a[3:]
array([3, 4, 5, 6, 7, 8, 9])
d=np.arange(25)
d = d.reshape((5,5))
d
array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24]])
d[3]
array([15, 16, 17, 18, 19])
d[:,3]
array([ 3, 8, 13, 18, 23])
b = d.transpose()
b[3]
array([ 3, 8, 13, 18, 23])
d[:2]
array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])
a = np.arange(5)
b = np.ones(5, dtype=int)
a
array([0, 1, 2, 3, 4])
b
array([1, 1, 1, 1, 1])
c = a+b[:,None]
c
array([[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]])
c + a
array([[1, 3, 5, 7, 9], [1, 3, 5, 7, 9], [1, 3, 5, 7, 9], [1, 3, 5, 7, 9], [1, 3, 5, 7, 9]])
c + a[:,None]
array([[1, 2, 3, 4, 5], [2, 3, 4, 5, 6], [3, 4, 5, 6, 7], [4, 5, 6, 7, 8], [5, 6, 7, 8, 9]])
Exercise 3) Create the $10 \times 10$ multiplication table. (Do not use 'for' loop.)
c
array([[1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5], [1, 2, 3, 4, 5]])
c == 1
array([[ True, False, False, False, False], [ True, False, False, False, False], [ True, False, False, False, False], [ True, False, False, False, False], [ True, False, False, False, False]], dtype=bool)
b
array([1, 1, 1, 1, 1])
mask = c <= b
mask
array([[ True, False, False, False, False], [ True, False, False, False, False], [ True, False, False, False, False], [ True, False, False, False, False], [ True, False, False, False, False]], dtype=bool)
d
array([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24]])
d[mask]
array([ 0, 5, 10, 15, 20])
np.where(mask)
(array([0, 1, 2, 3, 4], dtype=int64), array([0, 0, 0, 0, 0], dtype=int64))
True or False
True
True and False
False
mask1 = np.array([True,False,False,True,False])
mask2 = np.array([True,True,False,True,False])
mask1 or mask
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-56-df0b0dad315b> in <module>() ----> 1 mask1 or mask ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
mask1 and mask2
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-57-d93ac612a2e9> in <module>() ----> 1 mask1 and mask2 ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
np.logical_and(mask1, mask2)
array([ True, False, False, True, False], dtype=bool)
np.logical_or(mask1, mask2)
array([ True, True, False, True, False], dtype=bool)
d[0,3]=-1
d[2,2]=-10
d
array([[ 0, 1, 2, -1, 4], [ 5, 6, 7, 8, 9], [ 10, 11, -10, 13, 14], [ 15, 16, 17, 18, 19], [ 20, 21, 22, 23, 24]])
d.sum()
274
d.sum(0)
array([50, 55, 38, 61, 70])
d.mean(1)
array([ 1.2, 7. , 7.6, 17. , 22. ])
d.min(1)
array([ -1, 5, -10, 15, 20])
d.argmin(1)
array([3, 0, 2, 0, 0], dtype=int64)
Exercise 4) Create the 10x10 gaussian random number, and find the minimum value along the axis 0
In this section, we are going to learn a simple way to visualize data in interactive mode by using the matplotlib package. matplotlib package is based on the Matlab plot device.
First we change the default backend mode to interactive mode in the IPython console.
%matplotlib
Using matplotlib backend: Qt5Agg
If you use the Jupyter notebook like this manual, type the magic funcition like below:
%matplotlib notebook
or :
%matplotlib inline
Then import the pyplot module in matplotlib package.
import matplotlib.pyplot as plt
Now we draw the cosine and sine functiuons on the same plot from $0$ to $2\pi$.
theta = np.linspace(-np.pi, np.pi,151)
c = np.cos(theta)
s = np.sin(theta)
plt.plot(theta, c, lw = 2.5, label= r'cos($\theta$)')
plt.plot(theta, s, label= r'sin($\theta$)')
[<matplotlib.lines.Line2D at 0xf5bb4ef8d0>]
plt.xlim(-np.pi,np.pi) # set the x limit. Same with the xrange keyward in IDL plot processor
(-3.141592653589793, 3.141592653589793)
plt.ylim(-1,1)
(-1, 1)
plt.legend(loc=1)
<matplotlib.legend.Legend at 0xf5bbc774e0>
plt.tight_layout()
plt.savefig(r'C:\Tutorial\tri_fun.png')
plt.savefig(r'C:\Tutorial\tri_fun.eps')
Let's check the plot keywards.
plt.plot?
Exercise 5) Plot the tan function in red dashed line in $\theta$ range from $-\frac{2\pi}{5}$ to $\frac{2\pi}{5}$, and insert the legendary.
Before we start this section, we change the plot setting. First we change the tick direction.
plt.rc('xtick', direction='in')
plt.rc('ytick', direction='in')
plt.rc('xtick.minor',visible=True)
plt.rc('ytick.minor',visible=True)
fig, ax = plt.subplots(2, 2, figsize=(9,7))
x = np.linspace(-np.pi, np.pi, 15)
y = np.sin(x)
p1=ax[0,0].plot(theta, s, 'k')
ax[0,0].scatter(x, y, marker='o', c='b', edgecolors= 'b')
<matplotlib.collections.PathCollection at 0xf5bcca00b8>
ax[0,0].set_xlim(-np.pi,np.pi)
ax[0,0].set_ylim(-1.1,1.1)
(-1.1, 1.1)
ax[0,0].set_title('Moving spines')
Text(0.5,1,'Moving spines')
for i in ['top', 'right'] :
ax[0,0].spines[i].set_color('none')
ax[0,0].xaxis.set_ticks_position('bottom')
ax[0,0].yaxis.set_ticks_position('left')
for i in ['left', 'bottom'] :
ax[0,0].spines[i].set_position(('data',0))
p1.pop(0).remove()
yerr= np.sqrt(np.abs(y-0.5))+0.1
ax[0,1].errorbar(x, y, xerr=0.15, yerr=yerr,fmt='-o', capthick=1, capsize=5, color = 'k')
<Container object of 3 artists>
ax[0,1].set_title('Error plot')
Text(0.5,1,'Error plot')
gx= np.linspace(-5,5,500)
gy= np.linspace(-4,4,400)
gauss2d = np.exp(-0.5*(gx/gx.std())**2-0.5*(gy[:,None]/gy.std())**2)
gauss2d.shape
(400, 500)
im= ax[1,0].imshow(gauss2d)
im.remove()
im= ax[1,0].imshow(gauss2d, origin='lower', cmap=plt.cm.rainbow)
im.remove()
im= ax[1,0].imshow(gauss2d, origin='lower', cmap=plt.cm.rainbow, extent= [-5, 5, -2, 2])
cbar= plt.colorbar(im, ax=ax[1,0], pad=0)
cbar.set_label('Rainbow')
ax[1,0].set_title('Image plot')
Text(0.5,1,'Image plot')
ax[1,1].contourf(gx, gy, gauss2d, 5, cmap=plt.cm.rainbow)
<matplotlib.contour.QuadContourSet at 0xf5be7a6e48>
c= ax[1,1].contour(gx, gy, gauss2d, 5, colors='k')
ax[1,1].clabel(c)
<a list of 7 text.Text objects>
ax[1,1].set_title('Contour')
Text(0.5,1,'Contour')
fig.tight_layout(h_pad=0,w_pad=0)
In some cases, we may draw the zoomed a portion of image on the original image like below. In order to insert inset figure on the original image, we first import the mpl_toolkits.
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes, mark_inset
Then we download the sample image.
import os
dirn = r'C:\Tutorial\sample'
filen = 'goblin.png'
file=os.path.join(dirn,filen)
file
'C:\\Tutorial\\sample\\goblin.png'
goblin=plt.imread(file)
goblin.shape
(138, 141, 3)
fig = plt.figure(figsize=(6,6))
ax = plt.subplot(111)
im = ax.imshow(goblin, origin='lower')
xlim = (57, 78)
ylim = (32, 53)
axins = zoomed_inset_axes(ax, 2, loc = 4)
axins.imshow(goblin, origin='lower')
<matplotlib.image.AxesImage at 0xf5bb6fa5c0>
axins.set_xlim(xlim)
axins.set_ylim(ylim)
(32, 53)
axins.tick_params(left='off', right='off', top='off', bottom='off', labelleft='off', labelbottom='off')
mark_inset(ax, axins, loc1=1, loc2=3)
(<mpl_toolkits.axes_grid1.inset_locator.BboxPatch at 0xf5bed28f28>, <mpl_toolkits.axes_grid1.inset_locator.BboxConnector at 0xf5bb6da898>, <mpl_toolkits.axes_grid1.inset_locator.BboxConnector at 0xf5be6ad828>)
ax.annotate('Cute-eye', xy=(53, 70), xytext=(9,125), arrowprops=dict(fc='r', shrink=0.03), fontweight='bold')
Text(9,125,'Cute-eye')
from mpl_toolkits.axes_grid1 import make_axes_locatable
ax = plt.subplot(111)
im = ax.imshow(gauss2d, origin='lower', cmap= plt.cm.rainbow)
divider = make_axes_locatable(ax)
cax = divider.append_axes('right', size='5%', pad=0)
cbar = plt.colorbar(im, cax=cax)
cbar.set_label('Colorbar', fontweight='bold', fontsize=12)
In this section, we learn the useful function to analyze a data.
"A fast Fourier transform (FFT) is an algorithm that samples a signal over a period of time (or space) and divides it into its frequency components." (Wikipedia)
Basic fft functions are contained in scipy.fftpack module.
from scipy import fftpack
fftpack.fft
<function scipy.fftpack.basic.fft>
We download the example file, and apply fft to the data
example = r'C:/Tutorial/sample/sst_nino3.dat'
To read this we use the numpy.loadtxt() function.
data = np.loadtxt(example)
len(data)
504
plt.plot(data)
[<matplotlib.lines.Line2D at 0xf5bfa20828>]
fdata = fftpack.fft(data)
plt.plot(fdata)
C:\ProgramData\Anaconda3\lib\site-packages\numpy\core\numeric.py:531: ComplexWarning: Casting complex values to real discards the imaginary part return array(a, dtype, copy=False, order=order)
[<matplotlib.lines.Line2D at 0xf5bfaaf208>]
repro = fftpack.ifft(fdata)
plt.plot(repro)
C:\ProgramData\Anaconda3\lib\site-packages\numpy\core\numeric.py:531: ComplexWarning: Casting complex values to real discards the imaginary part return array(a, dtype, copy=False, order=order)
[<matplotlib.lines.Line2D at 0xf5c075ff98>]
fdata[50:452]=0
repro2 = fftpack.ifft(fdata)
plt.plot(repro2)
C:\ProgramData\Anaconda3\lib\site-packages\numpy\core\numeric.py:531: ComplexWarning: Casting complex values to real discards the imaginary part return array(a, dtype, copy=False, order=order)
[<matplotlib.lines.Line2D at 0xf5c0c02b00>]
We apply the gaussian convolution kernal to goblin image.
In this section, we use the fftconvolve function since it is faster than convolve function.
from scipy.signal import fftconvolve
kern = np.array([[1,2,1],[2,4,2],[1,2,1]])/16
convimg = [fftconvolve(goblin[:,:,i], kern, mode='same') for i in range(3)]
convimg = np.array(convimg)
convimg.shape
(3, 138, 141)
convimg=convimg.transpose((1,2,0))
convimg.shape
(138, 141, 3)
fig, ax = plt.subplots(1,2,figsize=(8,5))
ax[0].imshow(goblin)
<matplotlib.image.AxesImage at 0xf5c2028ac8>
ax[1].imshow(convimg)
<matplotlib.image.AxesImage at 0xf5c27b76d8>
Let's fit polynomial to data. Here, we use the nummpy polynomial() fitting function.
x = np.arange(len(data))
y = data.copy()
pfcoeff = np.polyfit(x, y, 1)
The polyfit function gives the fitting coefficients for given data.
pfcoeff
array([ 0.00052717, -0.1326027 ])
To create the polynomial fitting curve, we use the numpy.polyval() function.
pfval = np.polyval(pfcoeff, x)
Then, let's plot the original data and fitting curve
plt.plot(x, y, 'k', label='Real Data')
[<matplotlib.lines.Line2D at 0xf5c28fd3c8>]
plt.plot(x, pfval, 'r', label='Fitting Curve')
[<matplotlib.lines.Line2D at 0xf5c290ec88>]
plt.legend()
<matplotlib.legend.Legend at 0xf5c27aba90>
In, this sub-section, we fit sinosidal function to the data.
At first, we make a sinosidal function.
def func(theta, p0, p1, p2, p3):
return p0*np.sin(p1*theta+p2)+p3
Then import the curve_fit() function.
from scipy.optimize import curve_fit
Now we guass an initial value for fitting function
init = [2, 0.5, 0.1, -1]
xs = x[100:150]
ys = y[100:150]
Let's fit the given function to data.
coeff, cov = curve_fit(func, xs, ys, p0=init)
print(coeff)
[ 0.77459613 0.54645094 -4.62010486 0.22718251]
yfit = func(xs, *coeff)
plt.plot(xs, ys, 'k', label='Real Data')
[<matplotlib.lines.Line2D at 0xf5c07697b8>]
plt.plot(xs, yfit, 'r', label='Fitting Curve')
[<matplotlib.lines.Line2D at 0xf5c2dd9dd8>]
In general, many python user use the scipy interpolation module for interpolate data. However, this interpolation is somewhat slower than IDL's one, since the for loop in python is very slower than any other programming language. If there are many data which have to be applyed the interpolation, that take some time. In order to solve this slow speeding interpolation, there is interpolation package which is wriiten by using the Numba package.
Numba package make python code be Just-In-Time copiled to native machine instructions, similar in performance to C, C++ and Fortran.
At first we download the interpolation package. Excute the below command line on your terimal or cmd prompt:
conda install -c conda-forge interpolation
Before we use this interpolation package, we learn the general interpolation function which is included in scipy package.
from scipy.interpolate import interp1d
interpf = interp1d(xs, ys, kind='cubic')
xin = np.linspace(xs.min(), xs.max(), 3*len(xs))
yin = interpf(xin)
plt.plot(xs, ys, 'k', label= 'original')
[<matplotlib.lines.Line2D at 0xf5c2e1f240>]
plt.plot(xin, yin, 'r', label ='interpolated')
[<matplotlib.lines.Line2D at 0xf5c4b42d30>]
plt.legend()
<matplotlib.legend.Legend at 0xf5c4b64da0>
This package tool will be explained in YSSS tutorial.
from interpolation.splines import LinearSpline
First, define the grid boundaries
ny, nx, nc =goblin.shape
lower = np.array([0,0,0])
upper = np.array([ny-1,nx-1,nc-1])
order = np.array(goblin.shape)
interp = LinearSpline(lower, upper, order, goblin)
ya = np.linspace(0,ny-1,ny*2)
xa = np.linspace(0,nx-1,nx*2)
ca = np.arange(0,nc)
zero = np.zeros((2*ny,2*nx,nc))
yy = ya[:,None,None]+zero
xx = xa[:,None]+zero
cc = ca+zero
size = yy.size
size
233496
input_a = np.array([yy.reshape(size), xx.reshape(size), cc.reshape(size)])
out = interp(input_a.T)
res = out.reshape((2*ny,2*nx,nc))
plt.imshow(res)
<matplotlib.image.AxesImage at 0xf5c6f7bdd8>
In this section we lean how to find maximum or minimum of an objective function numerically.
We use one of minimization method, Amoeba method(= Nelder-Mead method = downhill simplex method)
from scipy.optimize import minimize
Let's derive the minimization solution of below function.
(1) Create the above function.
def minifunc(X) :
x, y = X
return 100*(y-x**2)**2+(1-x)**2
(2) Choose initial value.
X0 = [-0.5, -0.5]
res = minimize(minifunc, X0, method='Nelder-Mead')
res
final_simplex: (array([[ 1.00002709, 1.000054 ], [ 1.00000482, 1.00000328], [ 1.0000603 , 1.00011627]]), array([ 7.36938378e-10, 4.07353785e-09, 5.51563470e-09])) fun: 7.3693837805430572e-10 message: 'Optimization terminated successfully.' nfev: 139 nit: 75 status: 0 success: True x: array([ 1.00002709, 1.000054 ])
res['x']
array([ 1.00002709, 1.000054 ])
Exercies 6) Find the minimized value for below function by using Nelder-Mead method starting from (x,y)=(0,0).
In general, digital data have noise. In some case noise can highly affect the real signal. To supprese this, many researcher develope smooting methods. Among those smooting methods, Savitzky-Golay Filter is most famous way to reduce noise. Savitzky-Golay filter increases the signal-to-noise ratio without greatly distorting the signal. This is achieved, in a process known as convolution, by fitting successive sub-sets of adjacent data points with a low-degree polynomial by the method of linear least squares. (Wikipedia)
We use also sst_nino data in this section.
from scipy.signal import savgol_filter
sgfy = savgol_filter(y, 9, 3)
plt.plot(x,y,'k')
[<matplotlib.lines.Line2D at 0xf5c73f49e8>]
plt.plot(x, sgfy, 'r')
[<matplotlib.lines.Line2D at 0xf5c7394c18>]
Exercise 7) Apply the Savitzky-golay filter to sst_nino data with window length = 9 and polynomial order = 5. After that, overplot the result on the above figure. Finally, Fourier trnasform the result. Is there any diffrence with the previous result?
Download the sample sav file.
from scipy.io import readsav
filen = r'C:/Tutorial/sample/sample.sav'
sdata = readsav(filen)
sdata.keys()
dict_keys(['t', 'timeseries'])
t = sdata['t']
t.shape
(200,)
series = sdata['timeseries']
series.shape
(200, 3, 6)
plt.plot(t, series[:,:,1])
[<matplotlib.lines.Line2D at 0xf5c73b4f98>, <matplotlib.lines.Line2D at 0xf5c80ec940>, <matplotlib.lines.Line2D at 0xf5c80eca90>]
In order to read the fits file we need to AstroPy package.
In this section we only focous on the way to read fits file. First, we dowload example the fits file.
filen = r'C:/Tutorial/sample/502nmos.fits'
from astropy.io import fits
data = fits.getdata(filen)
header = fits.getheader(filen)
header['naxis1']
1600
data.shape
(1600, 1600)
plt.imshow(data,origin='lower',cmap=plt.cm.gray)
<matplotlib.image.AxesImage at 0xf5c8a03390>
plt.clim(0,30)
plt.draw()
In AstroPy package, there are many useful physical constants and units.
import astropy.constants as c
kb=c.k_B # Boltzmann constant
import astropy.units as u
T=1e3*u.K
E=kb*T
E.cgs
Sunpy is the python version Solar Software.
It is free and open source package written in Python.
However, this package is not developed package, but developing one. So, it may occur any errors at this time. To download this package run the below terminal command
conda install -c conda-forge sunpy
In this tutorial we skip this part, If you interested in this package and need to guide documentation, please check this SunPy website and its documentation