x-axis should be time-proportional in SAS - time

I need to present a linear x-axis, which means that distance between timepoints needs to be time-proportional. I have done the following code:
/*Produce mean +/- SD plot with line graph*/
proc sgplot data=adpc;
vline hours /response=value group=dose stat=mean limitstat=stderr;
xaxis label='Hours post dose';
yaxis label='Mean +/- SD';
run;
This is the output:
The x-axis has the variable hours which takes the values 0,1,2,3,4,6,8,24 (hours). I think to be time-proportional, means that it should have equal range between the observations. For example, x-axis should be 0,2,4,6,8,10,12,14,16,18,20,22,24 (not sure what time-proportional means).
what should i add?

The issue here is that VLINE is a categorical graph - like a bar graph but with lines. So the X Axis is a categorical axis! The only way to get a categorical axis to be proportional is to not skip any categories.
Better would be to use series or similar, which uses a numeric axis.
Here's an example using made up data (please provide that in the future!) and a HIGHLOW to add your bars.
data adpc;
call streaminit(7);
do dose = 1,5,10,100;
do _i = 1 to 100;
hours = rand('Integer',1,8);
if hours in (5,7) then hours=24;
value = rand('Uniform')*100*log(dose+1);
output;
end;
end;
run;
proc means data=adpc nway;
class dose hours;
var value;
output out=adpc_mean mean(value)=value stderr(value)=std;
run;
data adpc_calc;
set adpc_mean;
std_high = value+std;
std_low = value-std;
run;
proc sgplot data=adpc_calc;
series x=hours y=value/group=dose;
highlow x=hours high=std_high low=std_low/ lowcap=serif highcap=serif;
xaxis values=(1,2,3,4,6,8,24);
run;

Have you tried specifying the values in the XAXIS statement explicitly?
You'll need to list them all but this should give you the idea:
xaxis label='Hours post dose' values = ("0" "2" "4" "6" "8" "10" "12" ... "24");
EDIT: this more simplified version using the fake data from #Joe works well.
data adpc;
call streaminit(7);
do dose = 1,5,10,100;
do _i = 1 to 100;
hours = rand('Integer',1,8);
if hours in (5,7) then hours=24;
value = rand('Uniform')*100*log(dose+1);
output;
end;
end;
run;
proc sgplot data=adpc;
vline hours /response=value group=dose stat=mean limitstat=stderr;
xaxis label='Hours post dose' values = (0 to 24 by 2);
yaxis label='Mean +/- SD';
run;

Add the option type=time to your XAXIS statement. You will also be required to use the values= option to explicitly state the dosage values to be ticked.
Example:
proc sgplot data=have;
vline hours /response=value group=dose stat=mean limitstat=stderr;
xaxis label='Hours post dose'
type=time values=(0 to 4,6,8,24) /* added to xaxis statement */
;
yaxis label='Mean +/- SD';
run;
Full example:
data have;
call streaminit(2021);
do patid = 1 to 500;
dose = int( (patid-1) / (500/4) );
do hours = 0 to 4, 6, 8, 24;
select (dose);
when (0) value = hours/24 * 25;
when (1) value = hours/24 * 45;
when (2) value = ifn (hours<6, hours/6 * 100, 100 - hours/24 * 25);
when (3) value = ifn (hours<6, hours/6 * 250, 250);
otherwise;
end;
base = value;
jitter = rand('uniform') * hours;
value = min(jitter * value, 250);
output;
end;
end;
run;
ods html file='vline.html';
proc sgplot data=have;
vline hours /response=value group=dose stat=mean limitstat=stderr;
xaxis label='Hours post dose' type=time values=(0 to 4,6,8,24);
yaxis label='Mean +/- SD';
run;
ods html close;
Produces

Related

Title Loop within For Loop

I need to loop through the titles in the list and I cannot seem to do so without printing the entire list every time or looping through the graphs multiple times. Thanks in advance!
title = ["West", "Plains", "Midwest", "Southeast", "Northeast"]
State_list = [West_df,Plains_df,Midwest_df,Southeast_df,Northeast_df]
title = ["West", "Plains", "Midwest", "Southeast", "Northeast"]
State_list = [West_df,Plains_df,Midwest_df,Southeast_df,Northeast_df]
for index in range(len(State_list)):
#Plot bar chart with county and population
df_pivot = pd.pivot_table(State_list[index],values="Population",index="County",columns="State")
# Plot a bar chart using the DF
ax = df_pivot.plot(kind="bar")
fig = ax.get_figure()
mplcursors.cursor(fig)
# Change the plot dimensions (width, height)
fig.set_size_inches(15, 6)
# Change the axes labels
ax.set_xlabel("County")
ax.set_ylabel("Population")
plt.title(f"Region: {title} | Population Recorded in each County")
#Plot scatter plot with deaths per capita,
plt.figure(figsize=(10,8))
plt.xlabel("Cases of Covid")
plt.ylabel("Deaths per Capita")
(slope, intercept, rvalue, pvalue, stderr) = linregress(State_list[index]["Cases"], State_list[index]["Deaths per Capita"])
regress_values = State_list[index]["Cases"] * slope + intercept
line_eq = "y = " + str(round(slope,8)) + "x + " + str(round(intercept,5))
Scatter2 = plt.scatter(State_list[index]["Cases"], State_list[index]["Deaths per Capita"],s=State_list[index]["Heart Disease"]*.3,
c=State_list[index].State.astype('category').cat.codes)
plt.plot(State_list[index]["Cases"],regress_values,color = "purple")
plt.annotate(line_eq,(10000,.007),fontsize=15,color = "purple")
plt.annotate(f"The r-squared is: {rvalue**2}",(10000,.0067),fontsize=10,color = "purple")
plt.title(f"Region: {title} | Deaths per Capita recorded by County in regards to Heart Disease")
#Hover Event
mplcursors.cursor(Scatter2, hover=True)
plt.legend(loc = "upper right",ncol= 2, handles=Scatter2.legend_elements()[0],
labels=State_list[index],
title="State")

D3 - get selected Date range on brushend

I am new to d3 and using brushing on grouped bar chart
http://fiddle.jshell.net/CjaD3/21/
I am trying to get the range selected. I am listing to the "brushend" event and calling a function brushend(). Its getting called but returning me the x-axis coordinates in numbers ([42, 318]). I want in Date dormat like 'Sat 25' to 'Mon 27'
Thanks for your help.
This is where you would usually use a the invert method of your x-scale. Unfortunately, that method doesn't exist for ordinal scales. Luckily, Jason Davies, patch is still around. I don't like messing with the source, so I adapted it for your function:
function brushend() {
console.log("brushend");
var b = brush.empty() ? contextXScale.domain() : brush.extent();
console.log(b);
var d = mini_x0.domain(),
r = mini_x0.range(),
startDate = d[d3.bisect(r, b[0]) - 1],
finDate = d[d3.bisect(r, b[1]) - 1];
console.log([startDate, finDate]);
}
Updated fiddle.

D3.js time scale: nicely-spaced ticks at minute intervals when data is in seconds?

I'm working with a D3 time scale. My input data is in seconds, and it's duration data rather than dates - so 10 seconds, 30 seconds etc.
I want to create an axis that lets me do the following:
Display ticks formatted in minutes and seconds: like "0m 30s", "1m 00s", etc. This formatting on its own is fairly straightforward, but not when I also need to...
Display ticks at intervals that look neat when formatted in minutes. If I just use D3's default tick formatting then I get ticks at intervals that make sense in minutes, but not seconds.
Here is my code:
var values = [100,200,300....]; // values in seconds
var formatCount = d3.format(",.0f"),
formatTime = d3.time.format("%Mm %Ss"),
formatMinutes = function(d) {
var t = new Date(2012, 0, 1, 0, 0, d);
t.setSeconds(t.getSeconds() + d);
return formatTime(t);
};
var x = d3.scale.linear()
.domain([0, d3.max(values)])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(formatMinutes);
This gives me nicely-formatted ticks at irregular intervals: "16m 40s", "33m 20s" etc. How can I generate ticks at "10m 00s", "20m 00s", etc?
The obvious answer would be to transform the values array into minutes, use a linear scale and write a formatter to handle it, but I'd prefer to use a time scale if possible
Here is a JSFiddle to demonstrate the problem: http://jsfiddle.net/83Xmf/
Normally when making a time scale, you would use d3.time.scale(), rather than a linear scale.
Your case is a little odd in that you are using abstract durations of time, and not specific points in time for your data. Unfortunately it seems that d3's built in time functionality is not well-suited to this case. There are a couple of options I can think of for workarounds:
Option 1: Use a linear scale with manual .tickValues()
Rather than formatting your ticks using a Date object. You could simply break down your data value (which is in seconds) into hours, minutes, and seconds. Something like this:
formatMinutes = function(d) {
var hours = Math.floor(d / 3600),
minutes = Math.floor((d - (hours * 3600)) / 60),
seconds = d - (minutes * 60);
var output = seconds + 's';
if (minutes) {
output = minutes + 'm ' + output;
}
if (hours) {
output = hours + 'h ' + output;
}
return output;
};
Basically, this takes the total number of seconds, creates an hour for every 3600 seconds, creates a minute for each remaining 60 seconds, and finally gives back the remaining seconds. Then it outputs a string representation, for example: 17s or 12m 42s or 4h 8m 22s.
Then when you make your axis, you can use the .tickValues() method to assign a range from zero to your data's max value, going by steps of 600, since there are 600 seconds in 10 minutes. That would look like this:
var x = d3.scale.linear()
.domain([0, d3.max(values)])
.range([0, width]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(formatMinutes)
.tickValues(d3.range(0, d3.max(values), 600));
Here's a JSFiddle of the output.
Option 2: Use a time scale with a fixed duration for .ticks()
Time scales let you specify directly that you'd like ticks every 10 minutes. You do that simply by passing a d3 duration and a multiplier to the .ticks() method of your axis. Like this:
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.minute, 10)
In order to do this, you must first set up your time scale. For the domain of your scale, you can use a range of millisecond values, since d3 will turn these into Date objects. In this case, since your data is in seconds, we can simply multiply by 1000 to get milliseconds. In this case we'll round up the max value to the nearest millisecond, since it must be an integer to make a valid date:
var x = d3.time.scale()
.domain([0, Math.ceil(d3.max(values) * 1000)])
.range([0, width]);
Finally, you can pass your format in directly to the axis, using .tickFormat():
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.minute, 10)
.tickFormat(d3.time.format('%Mm %Ss'));
However, at this point I need to point something out because, as I mentioned, the built-in time functions are not well-suited to dealing with abstract durations. I'm going to change the .tickFormat to show the hours as well:
.tickFormat(d3.time.format('%Hh %Mm %Ss'));
Have a look at the JSFiddle of what the result would be...
Depending on where you are in the world, you'll get a different value for the hours place. I'm on the East coast of the US, so my hours place says 19. Where is that coming from? Shouldn't it be zero?
Well, unfortunately, when we made the domain of the scale go from 0 to the number of milliseconds of the largest data value, it created regular Date objects, using those values for the millisecond input. This means that they represent the number of milliseconds since midnight UTC time on January 1, 1970. Here in the Eastern time zone of the US, that means it was 19:00:00 on December 31, 1969. That's where the 19 comes from, or whatever other value you get.
If you know that all of your data will be less than 1 hour, then perhaps you can just ignore this. If you need to use an hours place, you can work around this by forcing d3 to use UTC time to format the axis using d3.time.format.utc():
.tickFormat(d3.time.format.utc('%Hh %Mm %Ss'))
Here's the JSFiddle updated to use UTC.
Now you can see that the hour is 0 as expected.
Of course, if any of your data is ever longer than 24 hours, this method won't work at all, and you'll have to resort to doing the axis manually as in Option 1.
Hopefully this helps to at least get you started, it's a tricky problem though, and there doesn't seem to be an elegant solution built into the library for handling this. Perhaps it would make for a good feature request on d3's git repo. I'd love to hear if #mbostock has any suggestions on how to handle abstract durations of time in d3 without having to be tied to Date objects, which require references to absolute points in time.

Setting tick intervals on a D3 time axis throwing an exception

I have a simple piece of JavaScript building a time axis in D3:
var xScale = d3.time.scale()
.domain([testData.datapoints[0].at, testData.datapoints[testData.datapoints.length - 1].at])
.range([0, (new Date(testData.datapoints[testData.datapoints.length - 1].at - testData.datapoints[0].at).getTime() / interval) * 21]),
xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks([d3.time.minute, 15]) /* This line causes an exception */
.tickFormat(d3.time.format("%d/%m/%y %H:%M:%S")),
In the D3 documentation on Time Scales ticks it says
While to create ticks at 15-minute intervals, say:
scale.ticks(d3.time.minute, 15);
But I get an exception:
Unhandled exception at line 9204, column 7 in d3.js
0x800a138a - JavaScript runtime error: Function expected
(N.B. moving the offending line from the xAxis chain to the xScale chain makes no difference, it still throws the same exception in the same place. Changing the offending line to
.ticks(20)
removes the exception but is not the functionality I am after.)
The D3 line where the error is thrown is the last line of this function in d3.js:
scale.ticks = function(interval, skip) {
var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ {
range: interval
}, skip ];
if (method) interval = method[0], skip = method[1];
return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip);
};
At the point the exception is thrown skip is undefined.
What's wrong with the line of D3 JavaScript? It is copied from the documentation, but I doubt it's a bug in D3, more likely I have misapplied it somehow.
You have this:
.ticks([d3.time.minute, 15])
But what you want is this:
.ticks(d3.time.minute, 15)
The array would be only if you're passing a specific list of values into the function. And in that case you'd want to use the .tickValues function

How to get quantize values

is there a way to get the start and end values of the quantizes of an quantize scale.
The range is defined by 5 colors ans the domain by d3.min and d3.max function on my data from an json file.
I need them for my legend of an choropleth map.
Thank you for helping.
Carsten
Thats my code
var quantizecolors = ["#d7191c","#fdae61","#ffffbf", "#a6d96a","#1a9641"];
var colorEnerg = d3.scale.quantize().range(quantizecolors);
colorEnerg.domain([
d3.min(collection.features, function(d){return d.properties.EB/d.properties.BEVZ;}),
d3.max(collection.features, function(d){return d.properties.EB/d.properties.BEVZ;})
]);
I assume that you're asking about the minimum and maximum domain values. Apart from saving them when you're setting them, you can also call colorEnerg.domain() without any values, which will return the array [min, max].
You can get the position of the breaks by computing the number and position of intervals:
var dom = colorEnerg.domain(),
l = (dom[1] - dom[0])/colorEnerg.range().length,
breaks = d3.range(0, colorEnerg.range().length).map(function(i) { return i * l; });

Resources