Altair: rotate shapes in scatterplot - altair

Using this as an example (my plot is actually a bit more complicated, therefore this example):
import altair as alt
from vega_datasets import data
source = data.stocks()
x = alt.Chart(source).mark_point().encode(
x='date',
y='price',
color='symbol',
shape=alt.Shape('symbol', scale=alt.Scale(range=['cross', 'circle', 'square', 'triangle-right', 'diamond']))
)
How can I use alt.Scale(range=['cross', ...]) and rotate some (for instance only the cross) of my symbols?
Thank you!

Try using the angle=alt.Angle() encoding channel. For example:
import altair as alt
from vega_datasets import data
source = data.stocks()
source['angle'] = 'angle-cat-one'
source.loc[source.symbol == 'GOOG', 'angle'] = 'angle-cat-two'
x = alt.Chart(source).mark_point().encode(
x='date',
y='price',
color='symbol',
shape=alt.Shape('symbol', scale=alt.Scale(range=['cross', 'circle', 'square', 'triangle-right', 'diamond'])),
angle=alt.Angle('angle:N', scale=alt.Scale(domain=['angle-cat-one', 'angle-cat-two'], range=[0, 45]))
)
x
Here the square-symbol is rotated in the chart based on the added angle column in the dataframe. I map the category values to a rotation value in degree using the a domain-range mapping in the scale of the angle encoding.
I noticed that this approach does not reflect the rotation in the legend.

Related

How to rotate the xticks in a Seaborn.objects V0.12x plot

By chance, is there a way to rotate the xticks in the graphic below (just to make it a bit more readable)? The usual
sns.xticks() doesnt work in the new Seaborn.objects development (which is amazing!)
tcap.\
assign(date_time2 = tcap['date_time'].dt.date).\
groupby(['date_time2', 'person']).\
agg(counts = ('person', 'count')).\
reset_index().\
pipe(so.Plot, x = "date_time2", y = "counts", color = "person").\
add(so.Line(marker="o", edgecolor="w")).\
label(x = "Date", y = "# of messages",
color = str.capitalize,
title = "Plot 2: Volume of messages by person, by day").\
scale(color=so.Nominal(order=["lorne_a_20014", "kayla_princess94"])).\
show()
In addition, my x-axis is categorical and this warning:
Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting. appears. I tried using:
import warnings
warnings.filterwarnings("ignore",category=UserWarning)
This can be done by creating an Axis object, rotating the axes there, and then using the so.Plot().on() method to apply the rotated-axis labels. Note this will not work if you also plan to add facets (I found your question while coming to ask about how to combine this with facets).
import seaborn.objects as so
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({'a':[1,2,3],
'b':[4,5,6]})
fig, ax = plt.subplots()
ax.xaxis.set_tick_params(rotation=90)
(so.Plot(df, x = 'a', y = 'b')
.add(so.Line())
.on(ax))

Drawing Worldmap Whose Center Is Japan With Geopandas

Before reading my question, my english skill is poor, so please send me feedback or advise in easy words. Thank you.
What I wand to do:
I want to draw an worldmap whose center is Japan with geopandas library on python 3.x.
My Environment:
Windows10 (64bit)
Python v3.9.4
geopandas v0.9.0
My Code:
import geopandas
world = geopandas.read_file(geopandas.datasets.get_path("naturalearth_lowres"))
world.boundary.plot(figsize=(15,8))
The Obtained Image
world image
Question:
The center of an obtained worldmap drawing is arouond Africa. I want to draw the image whose center is Japan. I read an official document: Mapping and Plotting Tools, but I can not find how to realize it. Please tell me advices !!
I found working with geopandas (+ pyproj as its dependency) to get the shifted map is too difficult. In my code below, geopandas is used to provide the geodataframe of the world to manipulate and plot. Cartopy is used to provide the geoaxis for proper geospatial referencing. And shapely is used to do all sorts of manipulation to transform geometries for plotting re-centered world plot to meet the requirements in the question.
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
from shapely.geometry import LineString, MultiPolygon, Polygon
from shapely.ops import split
from shapely.affinity import translate
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import geopandas
def shift_map(world_gdf, shift, ax):
# world_gdf: world geodataframe to shift
# shift: longitude shift
# ax: geoaxis to plot the map
shift -= 180
moved_map = []
splitted_map = []
border = LineString([(shift,90),(shift,-90)])
for row in world_gdf["geometry"]:
splitted_map.append(split(row, border))
for element in splitted_map:
items = list(element)
for item in items:
minx, miny, maxx, maxy = item.bounds
if minx >= shift:
moved_map.append(translate(item, xoff=-180-shift))
else:
moved_map.append(translate(item, xoff=180-shift))
gdf = geopandas.GeoDataFrame({"geometry": moved_map})
gdf.boundary.plot(ax=ax, linewidth=1, color='gray')
# can also use: gdf.plot() to plot the geometries as polygons
# define CRS's
crs0 = ccrs.PlateCarree(central_longitude=0) # standard CRS
lon_0 = 138 # Japan at center
# crsJapan = ccrs.PlateCarree(central_longitude=lon_0) # japan's centered; not in-use
# a special CRS for use with ax1.gridlines() to get correct longitude's labels plot
crsGridLines = ccrs.PlateCarree(central_longitude=-lon_0)
# create figure, axis
# use cartopy ccrs to get some niceties
fig, ax1 = plt.subplots(figsize=(8, 4.5), subplot_kw={"projection": crs0})
# load world geodataframe
world = geopandas.read_file(geopandas.datasets.get_path("naturalearth_lowres"))
# Plot the shifted map
shift_map(world, lon_0, ax1)
# Plot graticule/grid; only work with geoaxis
gl = ax1.gridlines(crs=crsGridLines, draw_labels=True, linewidth=1, color='gray', linestyle='--')
gl.xformatter = LONGITUDE_FORMATTER
gl.yformatter = LATITUDE_FORMATTER
gl.xlabel_style = {'size': 10, 'color': 'black'}
gl.ylabel_style = {'size': 10, 'color': 'black'}
plt.show()

Bokeh layout resize multiple plots

Using the example from https://github.com/bokeh/bokeh/blob/branch-2.4/examples/plotting/file/sizing_mode.py
I added a second plot. The sizing mode only seem to apply to the first plot.
Is there a trick to get both plots to respond to resize? Thanks
from bokeh.core.enums import SizingMode
from bokeh.layouts import column
from bokeh.models import Select
from bokeh.plotting import figure, output_file, show
from bokeh.sampledata.iris import flowers as df
colormap = {'setosa': 'red', 'versicolor': 'green', 'virginica': 'blue'}
colors = [colormap[x] for x in df.species]
plot = figure(sizing_mode="fixed")
plot.circle(df.petal_length, df.petal_width, color=colors, alpha=0.2, size=10)
plot2 = figure(sizing_mode="fixed")
plot2.circle(df.petal_length, df.petal_width, color=colors, alpha=0.2, size=10)
select = Select(title="Sizing mode", value="fixed", options=list(SizingMode), width=300)
select.js_link('value', plot, 'sizing_mode')
layout = column(select, plot, plot2)
layout.sizing_mode = "stretch_both" # set separately to avoid also setting children
output_file("sizing_mode.html", title="sizing_mode.py example")
show(layout)
I spotted my error in that code. Should have done js_link on layout instead of just the first plot.
select.js_link('value', layout, 'sizing_mode')
this works fine. thank you.

Matplotlib bar plot with table formatting

I have added a table to the bottom of my plot, but there are a number of issues with it:
The right has too much padding.
The left has too little padding.
The bottom has no padding.
The cells are too small for the text within them.
The table is too close to the bottom of the plot.
The cells belonging to the row names are not colored to match those of the bars.
I'm going out of my mind fiddling with this. Can someone help me fix these issues?
Here is the code (Python 3):
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
# Set styles
plt.style.use(['seaborn-paper', 'seaborn-whitegrid'])
plt.style.use(['seaborn'])
sns.set(palette='colorblind')
matplotlib.rc("font", family="Times New Roman", size=12)
labels = ['n=1','n=2','n=3','n=4','n=5']
a = [98.8,98.8,98.8,98.8,98.8]
b = [98.6,97.8,97.0,96.2,95.4]
bar_width = 0.20
data = [a,b]
print(data)
colors = plt.cm.BuPu(np.linspace(0, 0.5, len(labels)))
columns = ('n=1', 'n=2', 'n=3', 'n=4', 'n=5')
index = np.arange(len(labels))
plt.bar(index, a, bar_width)
plt.bar(index+bar_width+.02, b, bar_width)
plt.table(cellText=data,
rowLabels=['a', 'b'],
rowColours=colors,
colLabels=columns,
loc='bottom')
plt.subplots_adjust(bottom=0.7)
plt.ylabel('Some y label which effect the bottom padding!')
plt.xticks([])
plt.title('Some title')
plt.show()
This is the output:
Update
This is working now, but in case someone else is having issues: Make sure you are not viewing your plots and the changes you make to them with IntelliJ SciView as it does not represent changes accurately and introduces some formatting issues!
I think you can fix the first problem by setting the bounding box when you make the table using bbox like this:
bbox=[0, 0.225, 1, 0.2]
where the parameters are [left, bottom, width, height].
For the second issue (the coloring), that is because the color array is not corresponding to the seaborn coloring. You can query the seaborn color palette with
sns.color_palette(palette='colorblind')
this will give you a list of the colors seaborn is using.
Check the modifications below:
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
# Set styles
plt.style.use(['seaborn-paper', 'seaborn-whitegrid'])
plt.style.use(['seaborn'])
sns.set(palette='colorblind')
matplotlib.rc("font", family="Times New Roman", size=12)
labels = ['n=1','n=2','n=3','n=4','n=5']
a = [98.8,98.8,98.8,98.8,98.8]
b = [98.6,97.8,97.0,96.2,95.4]
bar_width = 0.20
data = [a,b]
colors = sns.color_palette(palette='colorblind')
columns = ('n=1', 'n=2', 'n=3', 'n=4', 'n=5')
index = np.arange(len(labels))
fig = plt.figure(figsize=(12,9))
plt.bar(index, a, bar_width)
plt.bar(index+bar_width+.02, b, bar_width)
plt.table(cellText=data,
rowLabels=[' a ', ' b '],
rowColours=colors,
colLabels=columns,
loc='bottom',
bbox=[0, 0.225, 1, 0.2])
fig.subplots_adjust(bottom=0.1)
plt.ylabel('Some y label which effect the bottom padding!')
plt.xticks([])
plt.title('Some title')
plt.show()
I also changed the subplot adjustment to subplot_adjust(bottom=0.1) because it wasn't coming out right otherwise. Here is the output:

Issue with drawparallels argument in Basemap

This seems like it should be an easy fix but I can't get it to work. I would like 40°N to display in the attached plot, but setting the labels argument in drawparallels to [1,0,1,1] isn't doing the trick. That should plot the parallels lables where they intersect the left, top and bottom of the plot according to the documentation. I would also like for 0° to once again show up in the bottom right corner. Any idea of how I can fix those 2 issues?
from netCDF4 import Dataset as NetCDFFile
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
from mpl_toolkits.basemap import addcyclic
nc = NetCDFFile('C:/myfile.nc')
lat = nc.variables['lat'][:]
lon = nc.variables['lon'][:]
time = nc.variables['time'][:]
olr = nc.variables['olr'][:]
olr,lon = addcyclic(olr,lon)
map = Basemap(llcrnrlon=0.,llcrnrlat=-40.,urcrnrlon=360.,urcrnrlat=40.,resolution='l')
lons,lats = np.meshgrid(lon,lat)
x,y = map(lons,lats)
levels = np.arange(-19.5,20.0,0.5)
levels = levels[levels!=0]
ticks = np.arange(-20.0,20.0,4.0)
cs = map.contourf(x,y,olr[0],levels, cmap='bwr')
cbar = plt.colorbar(cs, orientation='horizontal', cmap='bwr', spacing='proportional', ticks=ticks)
cbar.set_label('Outgoing Longwave Radiation Anomalies $\mathregular{(W/m^2)}$')
map.drawcoastlines()
map.drawparallels(np.arange(-40,40,20),labels=[1,0,1,1], linewidth=0.5, fontsize=7)
map.drawmeridians(np.arange(0,360,40),labels=[1,1,0,1], linewidth=0.5, fontsize=7)
The first part of the question is easy. In order for the label to show up, you have to actually draw the parallel, but np.arange(-40,40,20) does not include 40. So, if you change that statement to np.arange(-40,41,20) your 40N label will show up.
The second part should in principle be solvable in the same way, but Basemap apparently uses the modulo of the longitudes to compute the position of the labels, so just using np.arange(0,361,40) when drawing the meridians will result in two 0 labels on top of each other. However, we can capture the labels that drawmeridians generates and manually change the position of the second 0 label. The labels are stored in a dictionary, so they are easy to deal with. To compute the x position of the last label, I compute the difference in x-position between the first and the second label, multiply that with the amount of meridians to be drawn (360/40) and add the x-position of the first label.
Here the complete example:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.basemap import Basemap
map = Basemap(llcrnrlon=0.,llcrnrlat=-40.,urcrnrlon=360.,urcrnrlat=40.,resolution='l')
map.drawcoastlines()
yticks = map.drawparallels(
np.arange(-40,41,20),labels=[1,0,1,1], linewidth=0.5, fontsize=7
)
xticks = map.drawmeridians(
np.arange(0,361,40),labels=[1,1,0,1], linewidth=0.5, fontsize=7
)
first_pos = xticks[0][1][0].get_position()
second_pos = xticks[40][1][0].get_position()
last_x = first_pos[0]+(second_pos[0]-first_pos[0])*360/40
xticks[360][1][0].set_position((last_x,first_pos[1]))
plt.show()
Here the resulting plot:
Hope this helps.

Resources