Shared axis labels with independent scale - altair

When facet/concat-ing charts, I would like the axis labels to be shared (so only 1 label per column/row, here: Horsepower), but the scale to be independent. Is this possible?
I thought a combination of resolve_axis and resolve_scale would be the way to go, as the title is a part of Axis, but I didn't get it to work.
I'm also wondering what resolve_axis actually does different than resolve_scale, anyone has an example?
base = alt.Chart(source).mark_circle().encode(
x=alt.X('Horsepower:Q',),
y=alt.Y('Miles_per_Gallon:Q'),
color='Origin:N',
row=alt.Row('Origin:N'),
).properties(
width=200, height=100
)
base.resolve_axis(
x='shared' # doesn't do anything obvious
).resolve_scale(
x='independent'
)
Open the Chart in the Vega Editor

I found a hacky way to do this, by misusing the facet header:
base = alt.Chart(source).mark_circle(size=60).encode(
x=alt.X('Horsepower:Q',),
y=alt.Y('Miles_per_Gallon:Q',
axis=alt.Axis(title=''),),
color='Origin:N',
column=alt.Column('Origin:N', header=alt.Header(title='Miles_per_Gallon')),
).properties(
width=200, height=200
).configure_header(
labelExpr="['Origin',datum.value]",
titleOrient='left'
)
display(base.resolve_scale(y='shared'))
display(base.resolve_scale(y='independent'))

I don't know of any way to do what you're hoping for (independent scales with only a single outer axis title) via scale and guide resolution.
As to your question of the difference between resolve_scale and resolve_axis, an example may help.
Here's a chart with independent y scale:
import altair as alt
from vega_datasets import data
source = data.cars()
base = alt.Chart(source).mark_circle().encode(
x=alt.X('Horsepower:Q',),
y=alt.Y('Miles_per_Gallon:Q'),
color='Origin:N',
column=alt.Column('Origin:N'),
).properties(
width=150, height=150
)
base.resolve_scale(
y='independent'
)
And here's one with independent y axis:
base.resolve_axis(
y='independent'
)
In both cases, each chart gets its own axis (because independent scales imply independent axes), but only with an independent scale do the axes scales differ from each other.

Related

Is there a way to display the value of a mark next to the mark in Altair

I was playing around with the following example from the Altair Gallery:
https://altair-viz.github.io/gallery/airports_count.html
As of right now, the only way to display the actual count appears to be via the tooltip, as the example shows. However, I am trying to code a static visualization for which it would be very helpful if the exact value was displayed right next to the mark itself, without the user having to hover or interact in any way. Is there a way to achieve this?
You can do this by manually calculating offsets for text labels, though this is admittedly difficult when the points become crowded:
import altair as alt
from vega_datasets import data
airports = data.airports.url
states = alt.topo_feature(data.us_10m.url, feature='states')
# US states background
background = alt.Chart(states).mark_geoshape(
fill='lightgray',
stroke='white'
).properties(
width=500,
height=300
).project('albersUsa')
# airport positions on background
base = alt.Chart(airports).transform_aggregate(
latitude='mean(latitude)',
longitude='mean(longitude)',
count='count()',
groupby=['state']
).encode(
longitude='longitude:Q',
latitude='latitude:Q',
)
points = base.mark_circle().encode(
size=alt.Size('count:Q', title='Number of Airports'),
color=alt.value('steelblue'),
tooltip=['state:N','count:Q']
).properties(
title='Number of airports in US'
)
text = base.mark_text(
dx=15, dy=10
).encode(
text='count:Q'
)
background + points + text
Long-term, a better solution will be to use vega-label, which will be able to do this automatically once it's part of the Vega-Lite package. For Altair, this feature is tracked in this bug: https://github.com/altair-viz/altair/issues/1731

Having both X and Y axes' Scales in Altair respond to a selection interval

I'm bringing this question from Altair's github. (https://github.com/altair-viz/altair/issues/2456) Is there a way to get the Scale on Y-axis in the bottom chart to respond to the selection brush? I'd like to be able to pan around the top chart with a selection and see the zoomed-in results in the bottom chart. If I uncomment the alt.Y, then both the X and Y axes show Years and it's messed up. Is there a way to pass just an X or Y value in the 'brush' maybe? Thank you very much!
brush = alt.selection_interval(init={'x':[1950, 1970], 'y':[1500000, 2500000]}, encodings=['x', 'y'])
base = alt.Chart().mark_line().encode(
x=alt.X('Year:Q', title=None),
y='Deaths:Q',
color='Entity:N'
)
alt.vconcat(
base.add_selection(brush).encode().properties(height=150, width=150),
base.encode(
alt.X('Year:Q', scale=alt.Scale(domain=brush)),
#alt.Y('Deaths:Q', scale=alt.Scale(domain=brush)) # (un)commenting this line makes it work/fail only along the x-axis
).properties(
height=500, width=500
),
data='https://vega.github.io/vega-datasets/data/disasters.csv'
)
Yes, see Open the Chart in the Vega Editor
It filters the data of the 2nd chart using a filter transform on the brush param.

Ticks on color bar are overlapping because the values are very close to each other

I'm trying to display the exact values on one axis of the color bar and a basic scale on the other. However, some of the exact values are so close together their names overlap on the color bar. Is there a way for me to make the overlapping names appear as a list or just to the side the other values name? I've already tried rotation of the labels, setting vmin/vmax in the color bar method, and setting the ylim's of the second axis. I'm at a lose at what to try next. It feels like this is something matplotlib would allow but I can't find what method or kwargs that allow this manipulation. Many of the commented out tlines are the attempts I've made with help from many posts on StackOverflow. Thank you!!
Previous code deleted for clarity
UPDATE: Paul H here is a workable example with the same issue I'm trying to fix
# Make random data with same issue
x, y = np.linspace(-3, 1.5, 20), np.linspace(0, 0.5, 20)
# two different ranges used to simulate the same issue in my data
fake_phase = np.append(np.random.random_sample(15), np.arange(0.0, .005, 0.001))
fake_labels = np.array(['V439Oph', 'ALVir', 'YZVir', 'XXVir', 'V716Oph', 'BFSer', 'BLHer',
'RXLib', 'CEHer', 'V465Oph', 'V1180Sgr', 'CSCas', 'DQAnd', 'IXCas',
'UYEri', 'TWCap', 'AUPeg', 'MZCyg', 'SWTau', 'TXDel'], dtype=object)
# Plot data
fig, ax = plt.subplots(1,1,figsize=(15,10))
plt.tight_layout()
plt.plot(x, y, marker='.', ms=17, mew=2, linestyle='none')
# Make the same colorbar
norm = cm.colors.Normalize(vmin=0.0, vmax=1.0, clip=False)
cbar = fig.colorbar(cm.ScalarMappable(norm=norm, cmap='rainbow'), ax=ax, extend='both',
orientation='vertical', pad=0.005, use_gridspec=True)
cbar.set_ticks(fake_phase)
cbar.set_ticklabels(fake_labels)
cbar.ax.tick_params(which='major', labelsize='large', width=1.5, length=6)
cbar.set_label(label='Phase', size='xx-large', labelpad=40)
cbar.ax.set_aspect('auto')
ax2 = cbar.ax.twinx()
pos = cbar.ax.get_position()
pos.x0 += 0.1
ax2.set_position(pos)
plt.show();
The output of this code: Output of workable example
My issue is that the secondary axis on the colorbar (left axis) has values that are so close together their labels overlap. I'm hoping to find a way to space the labels so they are readable. I thought I found a way to accomplish this using axis.set_ticklabels() (set_ticklabels() documentation. In the **kargs section of the doc it references using text properties. In the text properties documentation text properties doc the property 'y' says you can set the y-position of the text. However, when I add this keyword to set_ticklabels() I get an error that the keyword is not recognized.. I've tried adding the property 'y' as a keyword and attribute but I get a keyword error or does not have that attribute error...
I'm calling the property wrong but I've never gotten this detailed in editing these parameters. I honestly don't know if this is the best way to solve this, but it's the closest I've gotten so far. I was hoping to use it to offset the labels so they were stacked vertically on top of each other in the same order but far enough apart that the label is readable.
Thanks for any input!

Plotting data with arbitrary center and x axis positive in both directions

I’m writing a paper and I have data showing structural changes in my molecule. Those structural changes are inverting the d orbital arrangements.
To show a gradual inversion I’d like to plot a graphic showing the perfect structure (all bonds = 2.1) as the center of my x-axis and to the left what happens to the orbitals if I gradually change two bonds from 2.1 to 2.2. To the right the axis is, also, positive, starting from the center 2.1 and going to 2.2 (but showing what happens with a change to only one bond).
Here is a drawing to clarify my intention:
So far, the best that I could achieve is this:
plt.figure(figsize=(9, 8))
dxy = sns.stripplot(x="Bond", y="Energy(cm-1)", data=dfbd, jitter=False, dodge=False, size=44, marker="_", linewidth=2, hue="Orbital")
plt.ylabel("Energy (Eh)")
If you want to give it a try or have any insights on how to achieve this I'll be extremely grateful.
Here is the data in CSV:
,Type,Bond,Orbital,Energy(cm-1)
0,D4h,2.1,dyz,0.0
1,D4h,2.1,dyz,1.2
2,D4h,2.1,dxz,6.0
3,D4h,2.1,dx2-y2,6473.1
4,D4h,2.1,dz2,6491.1
5,D4h,2.12,dxz,0.0
6,D4h,2.12,dyz,62.9
7,D4h,2.12,dxy,84.3
8,D4h,2.12,dz2,6233.7
9,D4h,2.12,dx2-y2,6560.8
10,D4h,2.14,dxz,0.0
11,D4h,2.14,dyz,125.9
12,D4h,2.14,dxy,171.2
13,D4h,2.14,dz2,5992.8
14,D4h,2.14,dx2-y2,6650.5
15,D4h,2.16,dxz,0.0
16,D4h,2.16,dyz,184.7
17,D4h,2.16,dxy,254.8
18,D4h,2.16,dz2,5761.5
19,D4h,2.16,dx2-y2,6736.9
20,D4h,2.18,dxz,0.0
21,D4h,2.18,dyz,239.4
22,D4h,2.18,dxy,335.4
23,D4h,2.18,dz2,5539.6
24,D4h,2.18,dx2-y2,6820.6
25,D4h,2.2,dxz,0.0
26,D4h,2.2,dyz,290.4
27,D4h,2.2,dxy,413.0
28,D4h,2.2,dz2,5327.0
29,D4h,2.2,dx2-y2,6901.5
30,D4h*,2.1,dyz,0.0
31,D4h*,2.1,dyz,1.2
32,D4h*,2.1,dxz,6.0
33,D4h*,2.1,dx2-y2,6473.1
34,D4h*,2.1,dz2,6491.1
35,D4h*,2.12,dxz,0.0
36,D4h*,2.12,dyz,29.0
37,D4h*,2.12,dxy,39.1
38,D4h*,2.12,dz2,6359.1
39,D4h*,2.12,dx2-y2,6514.3
40,D4h*,2.14,dxz,0.0
41,D4h*,2.14,dyz,60.6
42,D4h*,2.14,dxy,82.5
43,D4h*,2.14,dz2,6239.0
44,D4h*,2.14,dx2-y2,6559.1
45,D4h*,2.16,dxz,0.0
46,D4h*,2.16,dyz,90.0
47,D4h*,2.16,dxy,124.3
48,D4h*,2.16,dz2,6123.8
49,D4h*,2.16,dx2-y2,6602.2
50,D4h*,2.18,dxz,0.0
51,D4h*,2.18,dyz,117.3
52,D4h*,2.18,dxy,164.5
53,D4h*,2.18,dz2,6013.3
54,D4h*,2.18,dx2-y2,6643.9
55,D4h*,2.2,dxz,0.0
56,D4h*,2.2,dyz,142.9
57,D4h*,2.2,dxy,203.2
58,D4h*,2.2,dz2,5907.6
59,D4h*,2.2,dx2-y2,6684.2
Following #ImportanceOfBeingErnest's comment, I would tend to go for the "fake axis" route, as it avoids mucking around with several axes, several labels, legends, etc...
df.loc[:,'fake_Bond'] = df.Bond
df.loc[df.Type=='D4h', 'fake_Bond'] = 2.0 + abs(df.loc[df.Type=='D4h', 'Bond'] - 2.2)
plt.figure(figsize=(9, 8))
dxy = sns.stripplot(x="fake_Bond", y="Energy(cm-1)", data=df, jitter=False, dodge=False, size=44, marker="_", linewidth=2, hue="Orbital")
plt.ylabel("Energy (Eh)")
dxy.set_xticklabels(np.concatenate([np.linspace(2.2,2.1,6),np.linspace(2.12,2.2,5)]))
dxy.set_xlabel("D4h $\longleftarrow$ Bond $\longrightarrow$ D4h*")

how to control transparency of ppp density plot

I am trying to use the layered methods to overlay few spatstat spatial objects. All these objects are for the same window. I have an im layer (density) from a ppp. I want to make this layer a bit transparent in order to have a better visibility of the other objects in the layered object.
How can I control the transparency of this density plot (im)? Is there something like alpha or transparency parameter for the plot.im ?
UPDATE:
library(spatstat)
pipes=simplenet
plot(pipes)
point_net = as.ppp(runifpoint(10, win = Window(pipes)))
point_surface = density(point_net)
plot(point_surface)
layers= layered(point_surface, point_net, pipes)
plot(layers)
Here , I have plotted 3 layers. As you can see the density plot has very dark blues and reds. Yes, I can plot lines and points with different colours to make them visible, but it would nice to do simple stacked line, point plots and add a little bit of transparency to the density (im) plots.
The purpose is just to avoid complex customized plot colours and to explain to colleagues.
thank you.
First the commands from the original post:
library(spatstat)
pipes=simplenet
point_net = as.ppp(runifpoint(10, win = Window(pipes)))
point_surface = density(point_net)
layers= layered(point_surface, point_net, pipes)
plot(layers)
You need to provide a different colourmap to plot.im. There are two
ways you can do this:
Plot each layer individually using add = TRUE for subsequent
layers and provide the colour map when you plot the im object.
Pass a list of plot arguments when you plot the layered object you
have created above.
I find the first option easier for illustration, so I will do that
first. The default colourmap of spatstat is the 29th Kovesi colour
sequence (?Kovesi for more details on these sequences):
def_col <- Kovesi$values[[29]]
head(def_col)
#> [1] "#000C7D" "#000D7E" "#000D80" "#000E81" "#000E83" "#000E85"
To add transparency you can use to.transparent with your choice of
fraction for more/less transparency:
def_col_trans <- to.transparent(def_col, fraction = 0.7)
head(def_col_trans)
#> [1] "#000C7DB3" "#000D7EB3" "#000D80B3" "#000E81B3" "#000E83B3" "#000E85B3"
Now you just need to use this as your colourmap:
plot(point_surface, col = def_col_trans)
plot(point_net, add = TRUE)
plot(pipes, add = TRUE)
To do it with the layered object you have to make a list of plot
argument lists (containing NULL if you don't have additional
arguments):
layer_args <- list(list(col = def_col_trans),
list(NULL),
list(NULL))
plot(layers, plotargs = layer_args)

Resources