GithubHelp home page GithubHelp logo

jeffreysarnoff / rollingfunctions.jl Goto Github PK

View Code? Open in Web Editor NEW
112.0 4.0 5.0 3.03 MB

Roll a window over data; apply a function over the window.

License: MIT License

Julia 100.00%
rolling running windowed timeseries

rollingfunctions.jl's Introduction

RollingFunctions.jl

Roll a [weighted] function or run a statistic along windowed data.

Copyright © 2017-2024 by Jeffrey Sarnoff. Released under the MIT License.


IMPORTANT <<<

For now, rather than use e.g runmean, runcov, with version 0.8 use running(cov, ..) here is an example



Package Downloads


works with integers, floats, and missings

works with unweighted data

  • data that is a simple vector
  • data that is a CartesianIndexed vector

works with weights

  • weights given as a simple vector
  • weights given as a kind of StatsBase.AbstractWeights

applies functions (1-arg, .., 4-args)

  • applied over unweighted or weighted data

works with data matrices

  • same 1-arg function applied to each column

reasonable uses

  • with a simple vector
  • with a DataFrame column
  • with a TimeSeries column
  • with your own function

Rolling a function over data

With ndata = length(data), using a window of length windowsize, rolling a function results in a vector of ndata - windowsize + 1 elements. So there will be obtained windowsize - 1 fewer values than there are data values. All exported functions named with the prefix roll behave this way unless the keyword padding is given with the value to use for padding (e.g. missing). Using padding will fill the initial windowsize - 1 values with that padding value; the result will match the length of the data.

julia> data = collect(1.0f0:5.0f0); print(data)
Float32[1.0, 2.0, 3.0, 4.0, 5.0]
julia> windowsize = 3;

julia> result = rollmean(data, windowsize); print(result)
Float32[2.0, 3.0, 4.0]

julia> result = rollmean(data, windowsize; padding=missing); print(result)
Union{Missing, Float32}[missing, missing, 2.0, 3.0, 4.0]
julia> weights = normalize([1.0f0, 2.0f0, 4.0f0])
3-element Array{Float32,1}:
 0.21821788
 0.43643576
 0.8728715 
 
julia> result = rollmean(data, windowsize, weights); print(result)
Float32[1.23657, 1.74574, 2.25492]

julia> result = rollmean(data, windowsize, weights; padding=missing); print(result)
Union{Missing,Float32}[missing, missing, 1.23657, 1.74574, 2.25492]

Running a function over data

To obtain the same number of output data values as are given, the initial windowsize - 1 values output must be generated outside of the rolling behavior. This is accomplished by tapering the needed values -- using the same function, rolling it over successively smaller window sizes. All exported functions named with the prefix run behave this way.

julia> using RollingFunctions
julia> data = collect(1.0f0:5.0f0); print(data)
Float32[1.0, 2.0, 3.0, 4.0, 5.0]
julia> windowsize = 3;

julia> result = runmean(data, windowsize); print(result)
Float32[1.0, 1.5, 2.0, 3.0, 4.0]
julia> using RollingFunctions
julia> using LinearAlgebra: normalize

julia> weights = normalize([1.0f0, 2.0f0, 4.0f0]);
 
julia> result = runmean(data, windowsize, weights); print(result)
Float32[1.0, 1.11803, 1.23657, 1.74574, 2.25492]

rolling stats

  • rollmin, rollmax, rollmean, rollmedian
  • rollvar, rollstd, rollsem, rollmad, rollmad_normalized
  • rollskewness, rollkurtosis, rollvariation
  • rollcor, rollcov (over two data vectors)

running stats

  • runmin, runmax, runmean, runmedian
  • runvar, runstd, runsem, runmad, runmad_normalized
  • runskewness, runkurtosis, runvariation
  • runcor, runcov (over two data vectors)

Some of these use a limit value for running over vec of length 1.

works with functions over 1, 2, 3 or 4 data vectors

  • rolling(function, data1, data2, windowsize)

  • rolling(function, data1, data2, windowsize, weights) (weights apply to both data vectors)

  • rolling(function, data1, data2, windowsize, weights1, weights2)

  • rolling(function, data1, data2, data3, windowsize)

  • rolling(function, data1, data2, data3, windowsize, weights) (weights apply to all data vectors)

  • rolling(function, data1, data2, data3, windowsize, weights1, weights2, weights3)

  • running(function, data1, data2, data3, data4, windowsize)

  • running(function, data1, data2, data3, data4, windowsize, weights) (weights apply to all data vectors)

  • running(function, data1, data2, data3, data4, windowsize, weights1, weights2, weights3, weights4)

!! CHANGE ME !! Many statistical functions of two or more vector variables are not well defined for vectors of length 1. To run these functions and get an initial tapered value that is well defined, supply the desired value as firstresult.

  • running(function, data1, data2, windowsize, firstresult)
  • running(function, data1, data2, windowsize, weights, firstresult) (weights apply to both data vectors)

Philosophy and Purpose

This package provides a way for rolling and for running a functional window over data. Data is conveyed either as a vector or as a means of obtaining a vector from a matrix or 3D array or other data structure (e.g. dataframes, timeseries). Windows move over the data. One may use unweighted windows or windows wherein each position carries weight. Weighted windows apply the weight sequence through the window as it moves over the data.

When a window is provided with weights, the weights should must be normalized. We provide an algorithmically safe normalizing function that you may rely upon. Adding the sequence of normalized values one to the next obtains 1.0 or a value very slightly less than 1.0 -- their sum will not exceed unity. I do not know how to augment something already whole while respecting its integrity.

When running with a weighted window, the initial (first, second ..) values are determined using a tapering of the weighted window's span. This requires that the weights themselves be tapered along with the determinative function that is rolled. In this case, the weight subsequence is normalized (sums to one(T)), and that reweighting is used with the foreshortened window to taper that which rolls.

This software exists to simpilfy some of what you create and to faciliate some of the work you do.

Some who use it insightfully share the best of that. Others write words that smile.

All of this is expressed through the design of RollingFunctions.

Also Consider

  • The mapwindow function from ImageFiltering supports multidimensional window indexing and different maps.

rollingfunctions.jl's People

Contributors

jeffreysarnoff avatar staticfloat avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

rollingfunctions.jl's Issues

Rolling statistics between two+ vectors

You have done a really nice job creating a focused and very useful package, I have learned a lot by reading through the source code as I am fairly new to Julia.

Often times I find the need to do a rolling correlation, rolling least squares regression, etc, which involve calculations between two or more columns of a matrix instead of a single vector. Have you thought about this sort of rolling application before? I would be happy to help and contribute but wasn't sure if it's beyond the spirit of what your package is focused on or if it would fit.

Rolling does not respect type, converts to float

Function rolling returns float for int arguments, it should keep the type and not make conversions unless the aggregation function needs it.
See

julia>     data = 1:5
1:5

julia>     rolling(sum, data, 3)
3-element Vector{Float64}:
  6.0
  9.0
 12.0

julia>     [sum(data[i-2:i]) for i in 3:length(data)]
3-element Vector{Int64}:
  6
  9
 12

it would make sense for rolling to return ints instead of floats.

Unused type parameters

Julia 1.9 shows these warnings when testing the package. The redundant type parameters can just be removed.

WARNING: method definition for rolling at /home/pkgeval/.julia/packages/RollingFunctions/4Jh9c/src/roll/rolling.jl:44 declares type variable N but does not use it.
WARNING: method definition for rolling at /home/pkgeval/.julia/packages/RollingFunctions/4Jh9c/src/roll/rolling.jl:44 declares type variable N but does not use it.
WARNING: method definition for running at /home/pkgeval/.julia/packages/RollingFunctions/4Jh9c/src/run/running.jl:39 declares type variable N but does not use it.
WARNING: method definition for running at /home/pkgeval/.julia/packages/RollingFunctions/4Jh9c/src/run/running.jl:39 declares type variable N but does not use it.
WARNING: method definition for running at /home/pkgeval/.julia/packages/RollingFunctions/4Jh9c/src/run/running2.jl:80 declares type variable N but does not use it.
WARNING: method definition for running at /home/pkgeval/.julia/packages/RollingFunctions/4Jh9c/src/run/running2.jl:84 declares type variable N but does not use it.
WARNING: method definition for running at /home/pkgeval/.julia/packages/RollingFunctions/4Jh9c/src/run/running2.jl:80 declares type variable N but does not use it.
WARNING: method definition for running at /home/pkgeval/.julia/packages/RollingFunctions/4Jh9c/src/run/running2.jl:84 declares type variable N but does not use it.

Compatibility with CUDA ?

Currently rolling functions return an array on the cpu no matter the type of the input array. This means that the result must be moved back to the gpu after using it, which is a slow operation.

I guess this must be possible since the base diff function (equivalent to rolling(-,a,2)) supports CuArrays. But this may require good knowledge of CUDA.jl.

julia> using CUDA, RollingFunctions

julia> rolling(sum, cu(rand(10)), 2)
9-element Array{Float32,1}:
 1.5295954
 0.7749236
 1.0564828
 1.523129
 1.328017
 1.4198613
 1.3945057
 0.8062186
 0.24077797

Different API with `rolling` function

Hello,

I wonder if

mean(rolling(data, windowsize))

or with piping operator

rolling(data, windowsize) |> mean

couldn't be an interesting API

What is your opinion?

Kind regards

Problem with padding ?

Hi, I noticed that padding doesn't seem to work, either with the example given on the main page (as seen below) or with the test functions

julia> data = collect(1.0f0:5.0f0); print(data); windowsize = 3;
Float32[1.0, 2.0, 3.0, 4.0, 5.0]

julia> rollmean(data, windowsize; padding=missing);
ERROR: MethodError: no method matching rollmean(::Vector{Float32}, ::Int64; padding::Missing)

Closest candidates are:
  rollmean(::V, ::Int64) where {T, V<:AbstractVector{T}} got unsupported keyword argument "padding"
   @ RollingFunctions C:\Users\aubert.AD-IFSTTAR\.julia\packages\RollingFunctions\SJK7X\src\roll\rollstats.jl:14
  rollmean(::V, ::Int64, ::StatsBase.AbstractWeights) where {T, V<:AbstractVector{T}} got unsupported keyword argument "padding"
   @ RollingFunctions C:\Users\aubert.AD-IFSTTAR\.julia\packages\RollingFunctions\SJK7X\src\roll\rollstats.jl:18
  rollmean(::V, ::Int64, ::AbstractVector{S}) where {T, V<:AbstractVector{T}, S} got unsupported keyword argument "padding"
   @ RollingFunctions C:\Users\aubert.AD-IFSTTAR\.julia\packages\RollingFunctions\SJK7X\src\roll\rollstats.jl:16
  ...

Stacktrace:
 [1] top-level scope
   @ REPL[9]:1

(@v1.9) pkg> status
Status `C:\Users\aubert.AD-IFSTTAR\.julia\environments\v1.9\Project.toml`
  [b0e4dd01] RollingFunctions v0.7.0

julia> versioninfo()
Julia Version 1.9.0
Commit 8e63055292 (2023-05-07 11:25 UTC)
Platform Info:
  OS: Windows (x86_64-w64-mingw32)
  CPU: 8 × 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-14.0.6 (ORCJIT, tigerlake)
  Threads: 1 on 8 virtual cores

Overlap with `ImageFiltering.mapwindow`

There seems to be some overlap with ImageFiltering's mapwindow:

help?> ImageFiltering.mapwindow
  mapwindow(f, img, window, [border="replicate"]) -> imgf

  Apply f to sliding windows of img, with window size or indices specified by window. For example, mapwindow(median!, img, window) returns an Array of values similar to img
  (median-filtered, of course), whereas mapwindow(extrema, img, window) returns an Array of (min,max) tuples over a window of size window centered on each point of img.

  The function f receives a buffer buf for the window of data surrounding the current point. If window is specified as a Dims-tuple (tuple-of-integers), then all the integers must be odd
  and the window is centered around the current image point. For example, if window=(3,3), then f will receive an Array buf corresponding to offsets (-1:1, -1:1) from the imgf[i,j] for
  which this is currently being computed. Alternatively, window can be a tuple of AbstractUnitRanges, in which case the specified ranges are used for buf; this allows you to use
  asymmetric windows if needed.

  border specifies how the edges of img should be handled; see imfilter for details.

  For functions that can only take AbstractVector inputs, you might have to first specialize default_shape:

  f = v->quantile(v, 0.75)
  ImageFiltering.MapWindow.default_shape(::typeof(f)) = vec

  and then mapwindow(f, img, (m,n)) should filter at the 75th quantile.

  See also: imfilter.

One advantage of mapwindow is that it works on arbitrary-dimensional arrays.

I will put a link to RollingFunctions on ImageFiltering's README.

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Rolling Regression?

Hello, thank you for your commitment.

I want to know if RollingFunctions can be implemented when I have more than one variable.

Especially I want to use it to calculate rolling regression

Window and Data length

Currently, if I use a rolling function with a window length N and my vector is only of length M < N, I get an error. I'm using a FILL_FIRST, so I expected that those entries would just be filled as they would if my vector was longer.

Feature request: TimeArrays support

It would be nnatural to run rolling functions on (multidimensional) time-series (TimeArrays in Julia) and get TimeArray back. Thanks in advance.

trailing average ?

is it possible to compute using this package? i see OffsetWindow in src, but it is not documented.

Typo in type checking of 4th vector

I think the second "T3" must be a typo in the 4 vector version of functions (in src/roll/roll.jl)

One example:

function basic_rolling(window_fn::Function, data1::AbstractVector{T1}, data2::AbstractVector{T2}, data3::AbstractVector{T3}, data4::AbstractVector{T4},
    window_span::Int) where {T1,T2,T3,T4}
    typ = promote_type(T1, T2, T3, T4)
    ᵛʷdata1 = typ == T1 ? asview(data1) : asview(map(typ, data1))
    ᵛʷdata2 = typ == T2 ? asview(data2) : asview(map(typ, data2))
    ᵛʷdata3 = typ == T3 ? asview(data3) : asview(map(typ, data3))
    ᵛʷdata4 = typ == T3 ? asview(data4) : asview(map(typ, data4))

    basic_rolling(window_fn, ᵛʷdata1, ᵛʷdata2, ᵛʷdata3, ᵛʷdata4, window_span)
end

ᵛʷdata4 = typ == T3 ? asview(data4) : asview(map(typ, data4))
should probably be
ᵛʷdata4 = typ == T4 ? asview(data4) : asview(map(typ, data4))

no method matching AbstractFloat(::Type{Union{Missing, Float64}})

After upgrading to julia 1.0.3 from 1.0.2. I get error with code that used to work before:

A = [missing;1;2;3;4;5;0.000000001; -100;0; missing; NaN]
using RollingFunctions
running(sum, A, 2)
ERROR: MethodError: no method matching AbstractFloat(::Type{Union{Missing, Float64}})
Closest candidates are:
  AbstractFloat(::Bool) at float.jl:250
  AbstractFloat(::Int8) at float.jl:251
  AbstractFloat(::Int16) at float.jl:252
  ...
Stacktrace:
 [1] float(::Type) at ./float.jl:269
 [2] running(::Function, ::Array{Union{Missing, Float64},1}, ::Int64) at /home/gsc/.julia/packages/RollingFunctions/DLRrJ/src/running.jl:8
 [3] top-level scope at none:0

Adding RollingFunctions.float(::Type{Union{T,Missing}}) where {T} = Base.float(T) from JuliaLang/julia#29693 seems a partial workaround for me.

Version 0.8 broke runcov()

I just upgraded RollingFunctions.jl to 0.8.0 but found runcov() was broken. I have to downgrade to 0.7.0. Is it possibly due to the new padding feature?

runcov(rand(100),rand(100),10)

ERROR: MethodError: no method matching running(::typeof(Statistics.cov), ::Vector{Float64}, ::Vector{Float64}, ::Int64, ::Int64)

I just input one Int64 but the error message suggests two.

rolling regression

Could you add a rolling regression function that can return the regression coefficients and R squared value? With this function, your package would be super useful for time series analysis.

Add support for padding with sentinel value for roll*

Example:

julia> rollmean(1:5, 3)
3-element Vector{Float64}:
 2.0
 3.0
 4.0

julia> runmean(1:5, 3)
5-element Vector{Float64}:
 1.0
 1.5
 2.0
 3.0
 4.0

julia> [fill(missing, 2); rollmean(1:5, 3)]
5-element Vector{Union{Missing, Float64}}:
  missing
  missing
 2.0
 3.0
 4.0

It would be nice to add a kwarg to roll* to allow such padding, e.g. by writing rollmean(1:5, 3; pad=missing).

show me how you would like this to be in use

I have this opportunity with API design for your needs to be met.

macro maybe(T)
   :(Union{Missing, $T})
end
macro maybe(T1,T2)
   :(Union{Missing, $T1, $T2})
end

# ◇ is the modal logic symbol for ~"it is possible that"

const ◇ₙᵤₘ	= @maybe(Float64)
const ◇ᵢₙₜ	= @maybe(Int32, Int64)
const ◇ᵥₐₗᵤₑ	= @maybe(Real)

Our data comes in various forms and flavors. To help focus, let's begin with our data being a vector: vec::Vector{◇ₙᵤₘ}, a vector of some admixture of indicies holding values that are present and indicies that do not hold, are unvalued.

We have an applicative function that is computable applied to not less than β valued and present values. More elaborate constraints may be served.

One may gobble successive ◇ₙᵤₘ instances starting from the available past moving progressively, or from the most recent present moving retrogressively, or moving with more central balance (as in some filtering algorithms). Other modes of stepping are used -- a weekly trade-weighted forex measure steps by 7 days.

My first question: what do you want to see through the moving window?

First example from introduciton throws an error. Is padding still supported?

from: https://jeffreysarnoff.github.io/RollingFunctions.jl/dev/intro/padding/

Data = [1, 2, 3, 4, 5];
Func = sum;
Span = 3;
rolling(Func, Data, Span; padding = 0);

returns

MethodError: no method matching rolling(::typeof(sum), ::Vector{Int64}, ::Int64; padding=0)
Closest candidates are:
  rolling(::Function, ::AbstractVector{T}, ::Int64) where T at ~/.julia/packages/RollingFunctions/SJK7X/src/roll/rolling.jl:6 got unsupported keyword argument "padding"
  rolling(::Function, ::AbstractVector{T}, ::Int64, ::F) where {T, N<:Number, F<:Vector{N}} at ~/.julia/packages/RollingFunctions/SJK7X/src/roll/rolling.jl:19 got unsupported keyword argument "padding"
  rolling(::Function, ::AbstractVector{T}, ::Int64, ::W) where {T, W<:StatsBase.AbstractWeights} at ~/.julia/packages/RollingFunctions/SJK7X/src/roll/rolling.jl:44 got unsupported keyword argument "padding"

rollsum, runsum

Thanks for this package! I kindly request rollsum and runsum functions. And more documentation on the custom functions running(function, data, windowsize).

Padding example from readme not working

padding is not a valid keyword.

julia> data = collect(1.0f0:5.0f0)
5-element Vector{Float32}:
 1.0
 2.0
 3.0
 4.0
 5.0

julia> windowsize = 3
3

julia> result = rollmean(data, windowsize)
3-element Vector{Float32}:
 2.0
 3.0
 4.0

julia> result = rollmean(data, windowsize; padding=missing)
ERROR: MethodError: no method matching rollmean(::Vector{Float32}, ::Int64; padding=missing)
Closest candidates are:
  rollmean(::V, ::Int64) where {T, V<:AbstractVector{T}} at C:\Users\Henri\.julia\packages\RollingFunctions\SJK7X\src\roll\rollstats.jl:14 got unsupported keyword argument "padding"
  rollmean(::V, ::Int64, ::StatsBase.AbstractWeights) where {T, V<:AbstractVector{T}} at C:\Users\Henri\.julia\packages\RollingFunctions\SJK7X\src\roll\rollstats.jl:18 got unsupported keyword argument "padding"
  rollmean(::V, ::Int64, ::AbstractVector{S}) where {T, V<:AbstractVector{T}, S} at C:\Users\Henri\.julia\packages\RollingFunctions\SJK7X\src\roll\rollstats.jl:16 got unsupported keyword argument "padding"    
  ...
Stacktrace:
 [1] top-level scope
   @ REPL[12]:1

julia> result = rollmean(data, windowsize, rand(3); padding=missing)
ERROR: MethodError: no method matching rollmean(::Vector{Float32}, ::Int64, ::Vector{Float64}; padding=missing)
Closest candidates are:
  rollmean(::V, ::Int64, ::AbstractVector{S}) where {T, V<:AbstractVector{T}, S} at C:\Users\Henri\.julia\packages\RollingFunctions\SJK7X\src\roll\rollstats.jl:16 got unsupported keyword argument "padding"    
  rollmean(::V, ::Int64) where {T, V<:AbstractVector{T}} at C:\Users\Henri\.julia\packages\RollingFunctions\SJK7X\src\roll\rollstats.jl:14 got unsupported keyword argument "padding"
  rollmean(::V, ::Int64, ::StatsBase.AbstractWeights) where {T, V<:AbstractVector{T}} at C:\Users\Henri\.julia\packages\RollingFunctions\SJK7X\src\roll\rollstats.jl:18 got unsupported keyword argument "padding"
  ...
Stacktrace:
 [1] top-level scope
   @ REPL[13]:1

Upgrade to allow for missing values

Since the ecosystem seems to be moving towards missing values instead of NaN, I think it would be appropriate to incorporate those things here. I think it should be possible to just change all of the method signature endings from where T<:Number to where T<:Union{Number,Union{Number,Missing}}. I did this locally on rolling, rolling_fill_first, and roll_sum and was able to get roll_sum(FILL_FIRST,Int,missing,data) to work as expected.

I did not test any other cases (mostly just hacking this together so I can move on with my program), but my guess is that it will work for other cases as well.

Hope this is a helpful suggestion.

version 1

This is for discussion of issues specific to the design of version 1.
@bkamins

Option to pad initial values to preserve length?

Came up on discourse. Someone has a DataFrame,

df = DataFrame(y = [1, 2, 3, 4, 5, 6])

and they want to add a new column z where z = rolling(sum, df.y, 2).

Unfortunately, this gives a 5 element vector, so you can't use transform from DataFrames.

Can we add a keyword argument to add padding? Could hurt performance because it would change the type of output... but could be a good option to have.

FR: support for minimum window size

It would be nice if there was a way to specify a minimum window size for calculations in case not enough data is available. For example, a rollmean with windowsize of 3 and minimum window size of 2 would not calculate anything for the first entry of the input array, mean of the first 2 entries of the input array for the second item and mean of 3 entries going forward.
FWIW, Python's Pandas supports this with argument called min_periods: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.rolling.html

PS: I understand that the philosophy of this package is to keep it simple but to me this feels like something that would more naturally fit here instead of another "layer" package built "on top" of this one.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.