Coder Social home page Coder Social logo

sfcheung / semptools Goto Github PK

View Code? Open in Web Editor NEW
6.0 4.0 1.0 14.99 MB

Helper functions for customizing plots by semPlot::semPaths

Home Page: https://sfcheung.github.io/semptools

License: GNU General Public License v3.0

R 99.97% Rez 0.03%
diagram graph lavaan plot structural-equation-modeling r-package r sem

semptools's Introduction

Lifecycle: stable Project Status: Active - The project has reached a stable, usable state and is being actively developed. CRAN Version CRAN: Release Date CRAN RStudio mirror downloads Code size Last Commit at Master R-CMD-check

(Version 0.2.10.1, updated on 2023-11-12, release history)

semptools

Helper functions for modifying (postprocessing) plots generated by semPlot::semPaths() from the semPlot package.

Installation

The latest stable version can be installed from CRAN:

install.packages("semptools")

The latest development version at GitHub can be installed by remotes::install_github():

remotes::install_github("sfcheung/semptools")

To read the guides (vignettes) on how to use the functions, you can build the vignettes locally when installing the package:

remotes::install_github("sfcheung/semptools", build_vignettes = TRUE)

You can also find the guides under Articles of the Github page of this package.

Background

semPlot::semPaths() is a very useful function for visualizing structural equation models. We use it a lot. The output is a qgraph object which is highly customizable. Our area is in psychology and some users in this area may not know how to customize the graphs in aspects relevant to psychology. Therefore, we think it would be useful for users in psychology, including us, to have some functions for customizing the graphs from semPlot::semPaths(), without knowing the technical details of qgraph.

Philosophy

We think about the tasks we usually want to do with an semPlot::semPaths() graph, and write one function for each task. We write the functions such that all of them work by postprocessing a semPlot::semPaths() graph: receive an semPlot::semPaths() graph, modify it, and return a modified semPlot::semPaths() graph. This also allows users to use the %>% (pipe) operator from the magrittr package or the native pipe operator |> available since R 4.1.x to chain together modifications. For example:

modified_graph <- original_graph %>%
                    task_1() %>%
                    task_2(other_arguments) %>%
                    task_3()

In psychology, two typical models are confirmatory factor analysis model and structural models with latent factors. Therefore, we also wrote two functions, one for each model, that can combine several common tasks together, such as specifying the positions of the latent factors and adjusting the positions of the indicators.

We also write the functions in a way that users do not need to know the technical detail (e.g., the position of the path in the list of all paths). For example, if a user wants to move the path coefficient of the path from x to y closer to y, the user only needs to tell the function that it is the path from x to y. The function will find which path it is in the qgraph object.

What we have so far

These are some of the functions included so far

  • mark_se(): Add the standard errors to parameter estimates.

  • mark_sig(): Add asterisks ("*", "**", "***") based on $p$-values of parameter estimates.

  • rotate_resid(): Rotate the residuals of selected variables.

  • set_curve(): Change the curvature of selected paths.

  • set_edge_label_position(): Move the parameter labels of selected paths along the paths.

  • change_node_label(): Change the labels of nodes.

  • drop_nodes() and keep_nodes(): Drop or keeps nodes (e.g., drop all control variables).

  • set_cfa_layout(): A function for typical confirmatory factor analysis models. It can be used for specifying the orders of the indicators and factors, specifying the positions of the factors, setting the curvatures of the interfactor covariances, set the position of all loadings, and setting the orientation of the model (down, left, up, or right).

  • set_sem_layout(): A function for typical SEM models. It can be used for specifying the orders of the indicators and factors, specifying the positions of the factors using a grid, specifying the orientation of each factor's indicators (down, left, up, right), fine tuning the positions of indicators of selected factor, setting the curvatures of selected paths, and specifying the position of all or selected loadings.

See the Get Started to learn more about these and other functions.

Status

This package is still under development. There will be bugs, and there are limitations. Please post your comments and suggestions as issues at GitHub.

semptools's People

Contributors

marklhc avatar sfcheung avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

mddavilap

semptools's Issues

Add support for 2nd order factors in `set_sem_layout()`

In indicator_order, use NA for the "indicators" of 2nd order factors. Then adding this

The attempts at commit 3e33ba9 tries to remove the need for NA. Actually, factors that appear in both indicator_factor and indicator_order will be changed to NA in indicator_order.

The following

    tmp <- sapply(residual_rotate, function(x) !is.na(x$node))
    residual_rotate <- residual_rotate[tmp]

can be added before

    semPaths_plot <- rotate_resid(semPaths_plot, residual_rotate)

will do.

To-Do

  • Write a few tests.

Problem with custom node labels

library(lavaan)
library(semPlot)
library(semptools)
mod_pa <- 
 'x1 ~~ x2
  x3 ~  x1 + x2
  x4 ~  x1 + x3
 '
fit_pa <- lavaan::sem(mod_pa, pa_example)

In semPaths() one can specify custom labels using the nodeLabels
argument.

# Use custom labels
m <- matrix(c("Var1",   NA,  NA,   NA,
              NA, "Var3",  NA, "Var4",
              "Var2",   NA,  NA,   NA), byrow = TRUE, 3, 4)
p_pa <- semPaths(fit_pa, whatLabels = "est",
                 sizeMan = 10,
                 edge.label.cex = 1.15,
                 style = "ram", 
                 nodeLabels = c("Var3", "Var4", "Var1", "Var2"), 
                 layout = m)

However, using custom labels result in errors from mark_sig() and
similarly mark_se(), because the node labels are different from the
ones in the lavaan object.

p_pa2 <- mark_sig(p_pa, fit_pa)
plot(p_pa2)  # No "***" added

There may not be an easy fix as the qgraph object does not seem to
contain the original variable names. One option is to have users input
the custom labels as a named list. Another is to print out a warning
cautioning that the use of custom labels is not supported.

It is solvable if users specify a named vector as custom labels

# Use custom labels with named vector
p_pa <- semPaths(fit_pa, whatLabels = "est",
                 sizeMan = 10,
                 edge.label.cex = 1.15,
                 style = "ram", 
                 nodeLabels = c(
                   "x3" = "Var3", 
                   "x4" = "Var4", 
                   "x1" = "Var1", 
                   "x2" = "Var2"), 
                 layout = m)

In which case the mapping of the variable names and custom labels are
stored

p_pa$graphAttributes$Nodes$names
##     x3     x4     x1     x2 
## "Var3" "Var4" "Var1" "Var2"

P.S. A similar issue occurs when semPaths() abbreviate the names of
the variables.

`set_cfa_layout()`: Cannot handle a CFA plot with no factor covariances

To-Dos

  • Write a test.
  • Revise set_cfa_layout().
  • Check if set_sem_layout() has a similar problem.

Status

Work in orthogonal_cfa.

Note

There are cases in which the factors are orthogonal and so the covariances are not plotted. set_cfa_layout() will throw an error.

Check if factor covariances are present. If not, skip them.

A multiple-group issue

Hello Dr. Cheung and Dr. Lai,

This is a really cool and helpful package for customizing plots of path models in a convenient way with huge flexibility.

I was testing out this package with an output from a multigroup confirmatory factor analysis model which contains two path models of two groups of gender. The semPlot object contains two lists and the functions of semptools do not seem to handle semPlot objects with more than one list.

For example, after fitting a configural model into the data

configural_fit <- cfa(model = model_helpful, 
                      data = helpful, 
                      estimator = "WLSMV", 
                      ordered = c(paste0("H", 1:7)),
                      group = "gender",
                      meanstructure = TRUE, 
                      parameterization = "theta")
summary(configural_fit)

and plotting the path models via semPaths, which yields two plots,

conf_pa <- semPaths(configural_fit, whatLabels = "est", 
                    nCharNodes = 0, nCharEdges = 0)

passing through the object "conf_pa"

conf_pa_ast <- mark_sig(conf_pa, configural_fit)
plot(conf_pa_ast)

will yield an error message as shown below.

rlang::last_error()
<error/rlang_error_data_pronoun_not_found>
Column from_names not found in .data
Backtrace:

  1. semptools::mark_sig(conf_pa, configural_fit)
  1. rlang:::abort_data_pronoun(x)

rlang::last_trace()
<error/rlang_error_data_pronoun_not_found>
Column from_names not found in .data
Backtrace:

  1. ├─semptools::mark_sig(conf_pa, configural_fit)
  2. │ ├─dplyr::select(...)
  3. │ └─dplyr:::select.data.frame(...)
  4. │ └─tidyselect::vars_select(tbl_vars(.data), !!!enquos(...))
  5. │ └─tidyselect:::eval_select_impl(...)
  6. │ ├─tidyselect:::with_subscript_errors(...)
  7. │ │ ├─base::tryCatch(...)
  8. │ │ │ └─base:::tryCatchList(expr, classes, parentenv, handlers)
  9. │ │ │ └─base:::tryCatchOne(expr, names, parentenv, handlers[[1L]])
  10. │ │ │ └─base:::doTryCatch(return(expr), name, parentenv, handler)
  11. │ │ └─tidyselect:::instrument_base_errors(expr)
  12. │ │ └─base::withCallingHandlers(...)
  13. │ └─tidyselect:::vars_select_eval(...)
  14. │ └─tidyselect:::walk_data_tree(expr, data_mask, context_mask)
  15. │ └─tidyselect:::eval_c(expr, data_mask, context_mask)
  16. │ └─tidyselect:::reduce_sels(node, data_mask, context_mask, init = init)
  17. │ └─tidyselect:::walk_data_tree(new, data_mask, context_mask)
  18. │ └─base::eval(expr, data_mask)
  19. │ └─base::eval(expr, data_mask)
  20. │ ├─from_names
  21. │ └─rlang:::$.rlang_data_pronoun(.data, from_names)
  22. │ └─rlang:::data_pronoun_get(x, nm)
  23. └─rlang:::abort_data_pronoun(x)

If the lists inside of "conf_pa" are read one by one (e.g., by specifying conf_pa[[1]]), functions in this package work well.

conf_pa_ast <- mark_sig(conf_pa[[1]], configural_fit)
plot(conf_pa_ast)

Helper functions to make it easier to assign indicators to factors and set the orders of indicators

To-Dos

  • Write auto_indicator_config(). It returns a named vector of he indicator order, names being the factor names.
  • Modify set_sem_layout() and set_cfa_layou(): If either indicator_order or indicator_factor is not set, or both are not set, it will check whether (a) a lavaan model syntax is supplied, or (b) a lavaan-class object is supplied. If yes, it will adopt the appropriate option.!
  • Finalize auto_indicator_order().
  • Finalize add_object(), for future use.
  • Finalize lavaan_indicator_order().
  • Finalize changes in set_cfa_layout().
  • Finalize changes in set_sem_layout().

Status

Work in layout_helpers.

What have been done

  • A function to automatically determine the order based on the semPaths() output. The set_*_layout functions will use this function if indicator_order is not supplied.
  • The set_*_layout functions also accept a named vector for indicator_order. The names will be used for indicator_factor.
  • A function to convert a lavan model syntax order.

Option 1 (Not used)

In set_sem_layout and set_cfa_layout, users need to supply a character vector to match indicators to factors and set the orders. This can be improved by writing a helper function to generate the two vectors. E.g.,

x <- 
"
f2 =~ x1 + x3 + x4
f1 =~ x5 + x7 + x6
"

The function should generate these two vectors:

a1 <- c(""x1", "x3", "x4", "x5", "x7", "x6")
a2  <- c("f2", "f2", "f2", "f1", "f1", "f1")

Because users of semptools are also users of lavaan, it is easier for them to use syntax similar to that of lavaan.

Option 2 (Not used)

We used vectors as the input because the output of semPlot::semPaths() does not store the output of lavaan. However, we already have functions (e.g., mark_sig()) that asks for the output of lavaan.

Therefore, instead of asking users to specify the syntax again, we can just write simple helper functions to generate the vectors, instead of asking users to do the following:

indicator_order  <- c("x04", "x05", "x06", "x07",
                      "x01", "x02", "x03",
                      "x11", "x12", "x13", "x14",
                      "x08", "x09", "x10")
indicator_factor <- c( "f2",  "f2",  "f2",  "f2",
                       "f1",  "f1",  "f1",
                       "f4",  "f4",  "f4",  "f4",
                       "f3",  "f3",  "f3")

we can write two helpers to do this:

auto_indicator_order(fit)
auto_indicator_factor(fit)

These functions will inspect the parameter table and then generate these two vectors automatically.

Update quick_start_cfa.Rmd

Describe how to use named vectors or lists (an option added in v0.2.8.1) in vignettes. Update the quick starts because named list and vector should be the default way to specify the changes.

Note

No need to do this. set_cfa_layout does not need named vectors.

`factor_point_to`: Use named vector to set the argument

To-Dos

  • Write a test.
  • Draft auto_factor_point_to()

Status

Work in the branch auto_factor_point_to.

Note

This is from the vignette of layout_matrix()

point_to <- layout_matrix(left = c(1, 1),
                          left = c(3, 1),
                          down = c(2, 2),
                          up = c(2, 3))

How about using a layout matrix as input?

m_sem <- layout_matrix(f1 = c(1, 1),
                       f2 = c(3, 1),
                       f3 = c(2, 2),
                       f4 = c(2, 3))
m_sem
point_to <- auto_factor_point_to(factor_layout = m_sem,
                          f1 = "left", 
                          f2 = "left",
                          f3 = "down",
                          f4 = "up")

We can also revise set_sem_layout() and set_cfa_layout() to do this automatically if they detect that the argument of factor_point_to is a named vector rather than a matrix.

Allow `drop_nodes()` work with output from `lavaan::lavaanify()` without covariance input

Hi @sfcheung, I was using the semptools package and wanted to create a diagram just using lavaan syntax. The lavaanify() function allows converting syntax to a lavaan model object. I then ran into an error when using drop_nodes():

library(semptools)
m2 <- " eta_Y =~ 1 * fs_y
        eta_M =~ 1 * fs_m
        fs_y ~~ theta_y * fs_y
        fs_m ~~ theta_m * fs_m
        eta_Y ~ b2 * X + b3 * eta_M
        eta_M ~ b1 * X "
pm2 <- semPlot::semPlotModel(
    lavaan::lavaanify(m2)
)
pm2_2 <- drop_nodes(pm2, c("fs_y", "fs_m"))
#> Error in object@ObsCovs[[1]]: subscript out of bounds

Created on 2022-02-26 by the reprex package (v2.0.1)

Do you think we can make drop_nodes() change the ObsCovs and the ImpCovs slots only when the input semPlotModel contains those? I've added two conditional statements in a separate branch if you think it's okay to merge. Thanks!

Mark

Introduce all new functions for v0.2.9 (and those few in v0.2.8.1 not yet introduced) in the vignettes

DONE

Two tasks converted to issues: #83 , #82

To-Do

  • Describe how to use named vectors or lists (an option added in v0.2.8.1) in vignettes. Update the quick starts because named list and vector should be the default way to specify the changes.
  • Describe layout_matrix in quick start. (Do this in the branch vignette_layout_matrix) (ecb1628)
  • Describe drop_nodes and keep_nodes in quick start. (84057cf)
  • Describe change_node_label (added in v0.2.8.1) in vignettes. (a88ddb8)

Write some tests using testthat

The generated output is a qgraph object. Therefore, it is possible to write some tests for the functions to see if the changes to a qgraph is the intended ones.

Finalize the release of v0.2.9

Final checks before pushing v0.2.9 (work on this branch: devel_final_check

To-Do

  • Check the documentation of new functions.
  • Check the documentation of old functions. Include named vectors in the documentation of affected functions.
  • Proofread the vignettes.
  • Update NEWS.
  • Update DESCRIPTION.
  • Test by testthat.
  • Check by devtools::check.

Review where we are and plan ahead

To-Do

Models with intercepts are not yet supported

Note

  • A model with intercepts will have nodes not covered. Most semptools functions will return an error.
  • A model fitted by fiml in lavaan will have a mean structure, resulting in intercepts.
  • A workaround is setting intercepts = FALSE when using semPaths. All semptools functions should then work.

To-Do

  • Check which functions do not work with a model with intercepts.
  • Add a check to each of them.

Setting matrix for semPaths

Dear Dr. Cheung and Dr. Lai,

Thanks for writing these helper functions for users in psychology. I think these functions are really easy to use and they are very helpful for customizing graphs. I tested this package on my own project and everything works out pretty well. However, I am a little confused about setting the m matrix for semPlot in the following lines:

m <- matrix(c("x1",   NA,  NA,   NA,
                NA, "x3",  NA, "x4",
              "x2",   NA,  NA,   NA), byrow = TRUE, 3, 4)
p_pa <- semPaths(fit_pa, whatLabels = "est",
           sizeMan = 10,
           edge.label.cex = 1.15,
           style = "ram",
           nCharNodes = 0, nCharEdges = 0,
           layout = m)

I think adding an explanation about this m matrix and how to set it would be greatly appreciated by new users like me.

`semPaths(..., nCharNodes = 0)` does not give labels in a named vector, causing an error when calling `set_cfa_layout()` after `change_node_label()`

As illustrated below, semPaths() gives a named vector for the labels without specifying nCharNodes, but gives an unnamed vector when nCharNodes = 0.

library(lavaan)
#> This is lavaan 0.6-7
#> lavaan is BETA software! Please report any bugs.
library(semPlot)
#> Registered S3 methods overwritten by 'huge':
#>   method    from   
#>   plot.sim  BDgraph
#>   print.sim BDgraph
library(semptools)

mod_pa <- 
  'x1 ~~ x2
   x3 ~  x1 + x2
   x4 ~  x1 + x3
  '
fit_pa <- lavaan::sem(mod_pa, pa_example)
# Use custom labels
m <- matrix(c("x1",   NA,  NA,   NA,
              NA, "x3",  NA, "x4",
              "x2",   NA,  NA,   NA), byrow = TRUE, 3, 4)
p_pa <- semPaths(fit_pa, whatLabels = "est",
                 sizeMan = 10,
                 edge.label.cex = 1.15,
                 layout = m)

p_pa$graphAttributes$Nodes$labels
#>   x3   x4   x1   x2 
#> "x3" "x4" "x1" "x2"

p_pa2 <- semPaths(fit_pa,
  whatLabels = "est",
  nCharNodes = 0, 
  sizeMan = 10,
  edge.label.cex = 1.15,
  layout = m
)

p_pa2$graphAttributes$Nodes$labels
#> [1] "x3" "x4" "x1" "x2"

Created on 2020-12-31 by the reprex package (v0.3.0)

This causes an issue as when the labels are not named, the labels after applying change_node_labels() will also be unnamed, and the original variable names are lost, causing set_cfa_layout() and set_sem_layout() to fail.

A solution is to modify change_node_labels() to add the original labels as names when the original labels is an unnamed vector. I've made the changes and will push to a new branch before merging.

Test not passed on my machine (Ubuntu 20.04) due to `all.equal()` returns strings with curly double quotes

The build did not pass the test "Curve and edge label position can be changed after changing labels" on my Ubuntu machine. It seems that on @sfcheung's machine, the return value of

all.equal(p_pa2$graphAttributes$Edges, 
                      p_pa2_curve$graphAttributes$Edges)

is

Component \"curve\": Mean absolute difference: 1

but on my machine, the return value is

Component “curve”: Mean absolute difference: 1

(with curly double quotes).

I'll soon push a quick fix by replacing all curly double quotes to \" before passing the values to expect_match(), so that it should work on both machines

Update `change_node_label` to allow changing options related to drawing the labels

Note

When a label is changed, the original option such as font size may no longer be appropriate and needs to be changed. However, this can be done when a graph is first generated by semPlot::semPaths because the labels are not specified in this step. Using qgraph::qgraph again is not an option because it will change rebuild the graph and override changes made by semptools functions.

Address this issue in the branch update_change_node_label.

To-Do

  • Revise change_node_label to allow modifying graphAttributes$Nodes$label.cex.
  • Revise change_node_label to allow modifying plotOptions$label.scale.
  • Revise change_node_label to allow modifying plotOptions$label.prop.
  • Revise change_node_label to allow modifying plotOptions$label.norm.

Compatibility with R 4.0.0

Since R 4.0.0 was out, I tested whether the package can be installed with the newest version of R. So far it can be installed without issues, and runs fine with all vignettes built successfully. I'll continue to run a few tests on it.

Fix description of second argument in `mark_sig()` and `mark_se()`

The description currently says that it takes input

The object used by semPaths to generate the plot. Use the same argument name used in semPaths to make the meaning of this argument obvious.

However, I believe currently it only works for lavaan output objects, while semPaths() can be used on outputs other than lavaan outputs. Shall we update the descriptions?

push_edge_labels - A function to find overlapping edge labels (path parameters) and adjust their positions

Note

Sometimes semPaths draw two or more path coefficients on nearly the same position. It would be great to automatically move them away from each other. I have a rough idea on how to do this. May not be that difficult. Just receives a qgraph object, checks the implied positions for all edge labels, adjusts those with identical positions, and returns an adjusted vector of edge label positions.

This is not urgent because if the number of overlapping edge labels are small, this can be adjusted manually by set_edge_label_position.

2023-10-15: I think this is difficult. The drawing functions are internal functions and there is no simple way to know the product without actually drawing product.

Separate the pkgdown files (in docs) from the master branch

Status

  • DONE. At the master branch, created the GitHub action to build the pages at the branch gh-pages. Added /docs to .gitignore. Merged the commits to devel.

It should be possible to separate the pkgdown files (in docs) from the master branch, such that we can rebuild the site without updating the master branch.

2021-07-27: Tested in another package and it works. Will do this in the Version 0.2.9, after fixing the issue with change_node_label.

Missing support for SEM models with single manifest (non-indicator) variables.

Hi, I was hoping to use your package for my SEM model which includes single manifest variables in my regressions that are not indicators. I didn't find a way to integrate them into your framework. Did I miss something or is this just not possible yet? If it isn't, this would make the package very powerful. If it is, sorry in advance!

Update quick_start_sem.Rmd.

Describe how to use named vectors or lists (an option added in v0.2.8.1) in vignettes. Update the quick starts because named list and vector should be the default way to specify the changes.

TODO: Check compatibility with Mplus output

The semPaths() function can work with Mplus output (see here), although it's not clear to me whether it can extract parameter estimates. So it should work with the following functions that do not depend on a lavaan fit object:

  • rotate_resid()
  • set_curve()
  • set_edge_label_position()
  • set_cfa_layout()
  • set_sem_layout()

This is not urgent as the priority should be to make the functions work without bugs for lavaan.

When setting curve, positive means outward and negative means inward

To-Dos

  • Check. It seems that negative means expanding to the left or to the bottom, and positive means expanding to the right or to the top. If yes, we only need to update the documentation.

Note

Can use the position of the two nodes to determine the actual sign internally.

Write a function to build the layout matrix

In semPaths, to specify the layout of nodes, we need to prepare a matrix:

m <- matrix(c("x1",   NA,  NA,   NA,
                NA, "x3",  NA, "x4",
              "x2",   NA,  NA,   NA), byrow = TRUE, 3, 4)

This will become inefficient to create and modify if the number of variables is large. We can write a function to create the matrix this way:

m <- fct(x1 = c(2, 1),
         x2 = c(3, 1),
         x3 = c(2, 2),
         x4 = c(2, 4))

This may look less intuitive, but is much easier to modify when the number of variables is large.

Status

Done (bfd921e)

Write a function to convert a named list to the required list of arguments

Some functions require users to specify the changes as a list of lists. E.g.,

list(list(node = "x3", rotate =  45),
      list(node = "x4", rotate = -45),
      list(node = "x2", rotate = -90))

This can be difficult to read and write. We can write a function to convert a named list, or even a named vector, to this kind of list. E.g.,

# Option A:
fct(c(x3 = 45, x4 = -45, x2 = -90)) # Return the list in the block above.
# Option B:
fct(list(x3 = 45, x4 = -45, x3 = -90)) # Return the list in the block above.

It is better to write a separate function to do this, for two reasons.

  1. This function can be be used for all other semptools functions.

  2. We do not need to modify the existing functions.

DONE

  • Drafted, to_list_of_lists. Preliminary tests passed.

  • Functions revised (preliminary tests passed).

    • rotate_resid, for the argument rotate_resid_list.

    • set_curve, for the argument curve_list.

    • set_edge_label_position, for the argument position_list.

    • set_sem_layout, for the argument indicator_push, indicator_spread, and loading_position

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.